multiSSH3 5.42__tar.gz → 5.44__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.42 → multissh3-5.44}/PKG-INFO +1 -1
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.py +129 -142
- {multissh3-5.42 → multissh3-5.44}/README.md +0 -0
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.42 → multissh3-5.44}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.42 → multissh3-5.44}/setup.cfg +0 -0
- {multissh3-5.42 → multissh3-5.44}/setup.py +0 -0
|
@@ -45,10 +45,18 @@ except AttributeError:
|
|
|
45
45
|
# If neither is available, use a dummy decorator
|
|
46
46
|
def cache_decorator(func):
|
|
47
47
|
return func
|
|
48
|
-
version = '5.
|
|
48
|
+
version = '5.44'
|
|
49
49
|
VERSION = version
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
52
|
+
'~/multiSSH3.config.json',
|
|
53
|
+
'~/.multiSSH3.config.json',
|
|
54
|
+
'~/.config/multiSSH3/multiSSH3.config.json',
|
|
55
|
+
'/etc/multiSSH3.d/multiSSH3.config.json',
|
|
56
|
+
'/etc/multiSSH3.config.json'] # The first one has the highest priority
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# TODO: Add terminal TUI with history support
|
|
52
60
|
|
|
53
61
|
# ------------ Pre Helper Functions ----------------
|
|
54
62
|
def eprint(*args, **kwargs):
|
|
@@ -224,44 +232,42 @@ def load_config_file(config_file):
|
|
|
224
232
|
return {}
|
|
225
233
|
return config
|
|
226
234
|
|
|
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': [
|
|
235
|
+
if True:
|
|
236
|
+
AUTHOR = 'Yufei Pan'
|
|
237
|
+
AUTHOR_EMAIL = 'pan@zopyr.us'
|
|
238
|
+
DEFAULT_HOSTS = 'all'
|
|
239
|
+
DEFAULT_USERNAME = None
|
|
240
|
+
DEFAULT_PASSWORD = ''
|
|
241
|
+
DEFAULT_IDENTITY_FILE = None
|
|
242
|
+
DEDAULT_SSH_KEY_SEARCH_PATH = '~/.ssh/'
|
|
243
|
+
DEFAULT_USE_KEY = False
|
|
244
|
+
DEFAULT_EXTRA_ARGS = None
|
|
245
|
+
DEFAULT_ONE_ON_ONE = False
|
|
246
|
+
DEFAULT_SCP = False
|
|
247
|
+
DEFAULT_FILE_SYNC = False
|
|
248
|
+
DEFAULT_TIMEOUT = 50
|
|
249
|
+
DEFAULT_CLI_TIMEOUT = 0
|
|
250
|
+
DEFAULT_REPEAT = 1
|
|
251
|
+
DEFAULT_INTERVAL = 0
|
|
252
|
+
DEFAULT_IPMI = False
|
|
253
|
+
DEFAULT_IPMI_INTERFACE_IP_PREFIX = ''
|
|
254
|
+
DEFAULT_INTERFACE_IP_PREFIX = None
|
|
255
|
+
DEFAULT_NO_WATCH = False
|
|
256
|
+
DEFAULT_CURSES_MINIMUM_CHAR_LEN = 40
|
|
257
|
+
DEFAULT_CURSES_MINIMUM_LINE_LEN = 1
|
|
258
|
+
DEFAULT_SINGLE_WINDOW = False
|
|
259
|
+
DEFAULT_ERROR_ONLY = False
|
|
260
|
+
DEFAULT_NO_OUTPUT = False
|
|
261
|
+
DEFAULT_NO_ENV = False
|
|
262
|
+
DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
|
|
263
|
+
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
264
|
+
DEFAULT_JSON_MODE = False
|
|
265
|
+
DEFAULT_PRINT_SUCCESS_HOSTS = False
|
|
266
|
+
DEFAULT_GREPPABLE_MODE = False
|
|
267
|
+
DEFAULT_SKIP_UNREACHABLE = True
|
|
268
|
+
DEFAULT_SKIP_HOSTS = ''
|
|
269
|
+
SSH_STRICT_HOST_KEY_CHECKING = False
|
|
270
|
+
ERROR_MESSAGES_TO_IGNORE = [
|
|
265
271
|
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
266
272
|
'Connection to .* closed',
|
|
267
273
|
'Warning: Permanently added',
|
|
@@ -269,80 +275,34 @@ __build_in_default_config = {
|
|
|
269
275
|
'disabling multiplexing',
|
|
270
276
|
'Killed by signal',
|
|
271
277
|
'Connection reset by peer',
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
278
|
+
]
|
|
279
|
+
_DEFAULT_CALLED = True
|
|
280
|
+
_DEFAULT_RETURN_UNFINISHED = False
|
|
281
|
+
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
282
|
+
_DEFAULT_NO_START = False
|
|
283
|
+
_etc_hosts = {}
|
|
284
|
+
_sshpassPath = None
|
|
285
|
+
_sshPath = None
|
|
286
|
+
_scpPath = None
|
|
287
|
+
_ipmitoolPath = None
|
|
288
|
+
_rsyncPath = None
|
|
289
|
+
_shellPath = None
|
|
290
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX =None
|
|
291
|
+
__DEBUG_MODE = False
|
|
287
292
|
|
|
288
293
|
# Load Config Based Default Global variables
|
|
289
294
|
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
|
-
|
|
295
|
+
__configs_from_file = {}
|
|
296
|
+
for config_file in reversed(CONFIG_FILE_CHAIN.copy()):
|
|
297
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(config_file)))
|
|
298
|
+
globals().update(__configs_from_file)
|
|
336
299
|
# 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
300
|
if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
339
|
-
eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX
|
|
340
|
-
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(
|
|
301
|
+
eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX, ignoring ERROR_MESSAGES_TO_IGNORE')
|
|
302
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__ERROR_MESSAGES_TO_IGNORE_REGEX)
|
|
341
303
|
else:
|
|
342
304
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
343
305
|
|
|
344
|
-
__DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config['__DEBUG_MODE'])
|
|
345
|
-
|
|
346
306
|
# Load mssh Functional Global Variables
|
|
347
307
|
if True:
|
|
348
308
|
__global_suppress_printout = False
|
|
@@ -355,7 +315,6 @@ if True:
|
|
|
355
315
|
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
356
316
|
__keyPressesIn = [[]]
|
|
357
317
|
_emo = False
|
|
358
|
-
_etc_hosts = __configs_from_file.get('_etc_hosts', __build_in_default_config['_etc_hosts'])
|
|
359
318
|
__curses_global_color_pairs = {(-1,-1):1}
|
|
360
319
|
__curses_current_color_pair_index = 2 # Start from 1, as 0 is the default color pair
|
|
361
320
|
__curses_color_table = {}
|
|
@@ -394,12 +353,11 @@ if __curses_available:
|
|
|
394
353
|
_binPaths = {}
|
|
395
354
|
def check_path(program_name):
|
|
396
355
|
global __configs_from_file
|
|
397
|
-
global __build_in_default_config
|
|
398
356
|
global _binPaths
|
|
399
357
|
config_key = f'_{program_name}Path'
|
|
400
358
|
program_path = (
|
|
401
359
|
__configs_from_file.get(config_key) or
|
|
402
|
-
|
|
360
|
+
globals().get(config_key) or
|
|
403
361
|
shutil.which(program_name)
|
|
404
362
|
)
|
|
405
363
|
if program_path:
|
|
@@ -1420,12 +1378,14 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 5):
|
|
|
1420
1378
|
__handle_reading_stream(io.BytesIO(stderr),host.stderr, host)
|
|
1421
1379
|
# if the last line in host.stderr is Connection to * closed., we will remove it
|
|
1422
1380
|
host.returncode = proc.poll()
|
|
1423
|
-
if
|
|
1381
|
+
if host.returncode is None:
|
|
1424
1382
|
# process been killed via timeout or sigkill
|
|
1425
1383
|
if host.stderr and host.stderr[-1].strip().startswith('Timeout!'):
|
|
1426
1384
|
host.returncode = 124
|
|
1427
1385
|
elif host.stderr and host.stderr[-1].strip().startswith('Ctrl C detected, Emergency Stop!'):
|
|
1428
1386
|
host.returncode = 137
|
|
1387
|
+
else:
|
|
1388
|
+
host.returncode = -1
|
|
1429
1389
|
host.output.append(f'Command finished with return code {host.returncode}')
|
|
1430
1390
|
if host.stderr:
|
|
1431
1391
|
# filter out the error messages that we want to ignore
|
|
@@ -2713,14 +2673,45 @@ def generate_default_config(args):
|
|
|
2713
2673
|
'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
|
|
2714
2674
|
}
|
|
2715
2675
|
|
|
2716
|
-
def write_default_config(args,CONFIG_FILE
|
|
2717
|
-
if backup and os.path.exists(CONFIG_FILE):
|
|
2718
|
-
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
2676
|
+
def write_default_config(args,CONFIG_FILE = None):
|
|
2719
2677
|
default_config = generate_default_config(args)
|
|
2720
2678
|
# apply the updated defualt_config to __configs_from_file and write that to file
|
|
2721
2679
|
__configs_from_file.update(default_config)
|
|
2722
|
-
|
|
2723
|
-
json.
|
|
2680
|
+
if not CONFIG_FILE:
|
|
2681
|
+
print(json.dumps(__configs_from_file, indent=4))
|
|
2682
|
+
return
|
|
2683
|
+
backup = True
|
|
2684
|
+
if os.path.exists(CONFIG_FILE):
|
|
2685
|
+
eprint(f"Warning: {CONFIG_FILE!r} already exists, what to do? (o/b/n)")
|
|
2686
|
+
eprint(f"o: Overwrite the file")
|
|
2687
|
+
eprint(f"b: Rename the current config file at {CONFIG_FILE!r}.bak forcefully and write the new config file (default)")
|
|
2688
|
+
eprint(f"n: Do nothing")
|
|
2689
|
+
inStr = input_with_timeout_and_countdown(10)
|
|
2690
|
+
if (not inStr) or inStr.lower().strip().startswith('b'):
|
|
2691
|
+
backup = True
|
|
2692
|
+
elif inStr.lower().strip().startswith('o'):
|
|
2693
|
+
backup = False
|
|
2694
|
+
else:
|
|
2695
|
+
eprint("Aborted")
|
|
2696
|
+
sys.exit(1)
|
|
2697
|
+
try:
|
|
2698
|
+
if backup and os.path.exists(CONFIG_FILE):
|
|
2699
|
+
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
2700
|
+
except Exception as e:
|
|
2701
|
+
eprint(f"Error: Unable to backup the config file: {e!r}")
|
|
2702
|
+
eprint(f"Do you want to continue writing the new config file to {CONFIG_FILE!r}? (y/n)")
|
|
2703
|
+
inStr = input_with_timeout_and_countdown(10)
|
|
2704
|
+
if not inStr or not inStr.lower().strip().startswith('y'):
|
|
2705
|
+
eprint("Aborted")
|
|
2706
|
+
sys.exit(1)
|
|
2707
|
+
try:
|
|
2708
|
+
with open(CONFIG_FILE,'w') as f:
|
|
2709
|
+
json.dump(__configs_from_file,f,indent=4)
|
|
2710
|
+
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2711
|
+
except Exception as e:
|
|
2712
|
+
eprint(f"Error: Unable to write to the config file: {e!r}")
|
|
2713
|
+
eprint(f'Printing the config file to stdout:')
|
|
2714
|
+
print(json.dumps(__configs_from_file, indent=4))
|
|
2724
2715
|
|
|
2725
2716
|
# ------------ Wrapper Block ----------------
|
|
2726
2717
|
def main():
|
|
@@ -2732,11 +2723,12 @@ def main():
|
|
|
2732
2723
|
global _binPaths
|
|
2733
2724
|
global _env_file
|
|
2734
2725
|
global __DEBUG_MODE
|
|
2726
|
+
global __configs_from_file
|
|
2735
2727
|
_emo = False
|
|
2736
2728
|
# We handle the signal
|
|
2737
2729
|
signal.signal(signal.SIGINT, signal_handler)
|
|
2738
2730
|
# We parse the arguments
|
|
2739
|
-
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: {
|
|
2731
|
+
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}')
|
|
2740
2732
|
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)
|
|
2741
2733
|
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.')
|
|
2742
2734
|
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)
|
|
@@ -2773,7 +2765,9 @@ def main():
|
|
|
2773
2765
|
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)
|
|
2774
2766
|
|
|
2775
2767
|
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)
|
|
2776
|
-
parser.add_argument('--
|
|
2768
|
+
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')
|
|
2769
|
+
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)
|
|
2770
|
+
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')
|
|
2777
2771
|
parser.add_argument('--debug', action='store_true', help='Print debug information')
|
|
2778
2772
|
parser.add_argument('-ci','--copy_id', action='store_true', help='Copy the ssh id to the hosts')
|
|
2779
2773
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
@@ -2794,33 +2788,26 @@ def main():
|
|
|
2794
2788
|
eprint(f"Warning: Unknown arguments, treating all as commands: {unknown!r}")
|
|
2795
2789
|
args.commands += unknown
|
|
2796
2790
|
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
elif inStr.lower().strip().startswith('o'):
|
|
2811
|
-
write_default_config(args,CONFIG_FILE,backup = False)
|
|
2812
|
-
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2813
|
-
else:
|
|
2814
|
-
write_default_config(args,CONFIG_FILE,backup = True)
|
|
2815
|
-
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2816
|
-
except Exception as e:
|
|
2817
|
-
eprint(f"Error while writing config file: {e!r}")
|
|
2818
|
-
import traceback
|
|
2819
|
-
eprint(traceback.format_exc())
|
|
2820
|
-
if not args.commands:
|
|
2821
|
-
with open(CONFIG_FILE,'r') as f:
|
|
2791
|
+
if args.generate_config_file or args.store_config_file:
|
|
2792
|
+
if args.store_config_file:
|
|
2793
|
+
configFileToWriteTo = args.store_config_file
|
|
2794
|
+
if args.config_file:
|
|
2795
|
+
if os.path.exists(args.config_file):
|
|
2796
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
2797
|
+
else:
|
|
2798
|
+
eprint(f"Warning: Pre store config file {args.config_file!r} not found.")
|
|
2799
|
+
else:
|
|
2800
|
+
configFileToWriteTo = args.config_file
|
|
2801
|
+
write_default_config(args,configFileToWriteTo)
|
|
2802
|
+
if not args.commands and configFileToWriteTo:
|
|
2803
|
+
with open(configFileToWriteTo,'r') as f:
|
|
2822
2804
|
eprint(f"Config file content: \n{f.read()}")
|
|
2823
2805
|
sys.exit(0)
|
|
2806
|
+
if args.config_file:
|
|
2807
|
+
if os.path.exists(args.config_file):
|
|
2808
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
2809
|
+
else:
|
|
2810
|
+
eprint(f"Warning: Config file {args.config_file!r} not found, ignoring it.")
|
|
2824
2811
|
|
|
2825
2812
|
_env_file = args.env_file
|
|
2826
2813
|
__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
|