multiSSH3 4.97__py3-none-any.whl → 4.99__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.97.dist-info → multiSSH3-4.99.dist-info}/METADATA +62 -30
- multiSSH3-4.99.dist-info/RECORD +7 -0
- {multiSSH3-4.97.dist-info → multiSSH3-4.99.dist-info}/WHEEL +1 -1
- multiSSH3.py +135 -29
- multiSSH3-4.97.dist-info/RECORD +0 -7
- {multiSSH3-4.97.dist-info → multiSSH3-4.99.dist-info}/LICENSE +0 -0
- {multiSSH3-4.97.dist-info → multiSSH3-4.99.dist-info}/entry_points.txt +0 -0
- {multiSSH3-4.97.dist-info → multiSSH3-4.99.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.99
|
|
4
4
|
Summary: Run commands on multiple hosts via SSH
|
|
5
5
|
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -38,7 +38,7 @@ multissh will read a config file located at ```/etc/multiSSH3.config.json```
|
|
|
38
38
|
To store / generate a config file with the current command line options, you can use
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
mssh --
|
|
41
|
+
mssh --store_config_file
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
You can modify the json file directly after generation and multissh will read from it for loading defaults.
|
|
@@ -47,10 +47,14 @@ Note:
|
|
|
47
47
|
|
|
48
48
|
If you want to store password, it will be a plain text password in this config file. This will be better to supply it everytime as a CLI argument but you should really consider setting up priv-pub key setup.
|
|
49
49
|
|
|
50
|
+
Also Note:
|
|
51
|
+
|
|
52
|
+
On some systems, scp / rsync will require you use a priv-pub key to work
|
|
53
|
+
|
|
50
54
|
This option can also be used to store cli options into the config files. For example.
|
|
51
55
|
|
|
52
56
|
```bash
|
|
53
|
-
mssh --ipmi_interface_ip_prefix 192 --
|
|
57
|
+
mssh --ipmi_interface_ip_prefix 192 --store_config_file
|
|
54
58
|
```
|
|
55
59
|
will store
|
|
56
60
|
```json
|
|
@@ -64,6 +68,22 @@ DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
|
|
|
64
68
|
```
|
|
65
69
|
as hostname aliases.
|
|
66
70
|
|
|
71
|
+
multissh3 will resolve hostname grouping by:
|
|
72
|
+
ipv4 address expansion > local hostname resolution ( like /etc/hosts ) > currrent terminal environment > env from env_file > remote hostname resolution ( socket.gethostbyname() )
|
|
73
|
+
|
|
74
|
+
An example hostname alias file will look like:
|
|
75
|
+
```bash
|
|
76
|
+
us_east='100.100.0.1-3,us_east_prod_[1-5]'
|
|
77
|
+
us_central=""
|
|
78
|
+
us_west="100.101.0.1-2,us_west_prod_[a-c]_[1-3]"
|
|
79
|
+
us="$us_east,$us_central,$us_west"
|
|
80
|
+
asia="100.90.0-1,1-9"
|
|
81
|
+
eu=''
|
|
82
|
+
rhel8="$asia,$us_east"
|
|
83
|
+
all="$us,$asia,$eu"
|
|
84
|
+
```
|
|
85
|
+
( You can use bash replacements for grouping. )
|
|
86
|
+
|
|
67
87
|
For example:
|
|
68
88
|
```bash
|
|
69
89
|
export all='192.168.1-2.1-64'
|
|
@@ -104,61 +124,73 @@ While leaving minimum 40 characters / 1 line for each host display by default. Y
|
|
|
104
124
|
Use ```mssh --help``` for more info.
|
|
105
125
|
|
|
106
126
|
```bash
|
|
107
|
-
usage: mssh [-h] [-u USERNAME] [-
|
|
108
|
-
[-pre INTERFACE_IP_PREFIX] [-q] [-ww WINDOW_WIDTH] [-wh WINDOW_HEIGHT] [-sw] [-eo]
|
|
109
|
-
[--
|
|
110
|
-
|
|
127
|
+
usage: mssh [-h] [-u USERNAME] [-p PASSWORD] [-ea EXTRAARGS] [-11] [-f FILE] [-fs] [--scp] [-gm] [-t TIMEOUT] [-r REPEAT] [-i INTERVAL]
|
|
128
|
+
[--ipmi] [-mpre IPMI_INTERFACE_IP_PREFIX] [-pre INTERFACE_IP_PREFIX] [-q] [-ww WINDOW_WIDTH] [-wh WINDOW_HEIGHT] [-sw] [-eo]
|
|
129
|
+
[-no] [--no_env] [--env_file ENV_FILE] [-m MAX_CONNECTIONS] [-j] [--success_hosts] [-g] [-su] [-sh SKIP_HOSTS]
|
|
130
|
+
[--store_config_file] [--debug] [--copy-id] [-V]
|
|
131
|
+
[hosts] [commands ...]
|
|
111
132
|
|
|
112
|
-
Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command
|
|
133
|
+
Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file: /etc/multiSSH3.config.json
|
|
113
134
|
|
|
114
135
|
positional arguments:
|
|
115
|
-
hosts Hosts to run the command on, use "," to seperate hosts
|
|
116
|
-
commands the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host
|
|
136
|
+
hosts Hosts to run the command on, use "," to seperate hosts. (default: all)
|
|
137
|
+
commands the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host
|
|
138
|
+
name.
|
|
117
139
|
|
|
118
140
|
options:
|
|
119
141
|
-h, --help show this help message and exit
|
|
120
142
|
-u USERNAME, --username USERNAME
|
|
121
|
-
The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified.
|
|
122
|
-
|
|
123
|
-
Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.Use "=" ! Ex. -ea="--delete" (default:
|
|
124
|
-
None)
|
|
143
|
+
The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified.
|
|
144
|
+
(default: None)
|
|
125
145
|
-p PASSWORD, --password PASSWORD
|
|
126
|
-
The password to use to connect to the hosts, (default:
|
|
146
|
+
The password to use to connect to the hosts, (default: )
|
|
147
|
+
-ea EXTRAARGS, --extraargs EXTRAARGS
|
|
148
|
+
Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.Use "=" ! Ex.
|
|
149
|
+
-ea="--delete" (default: None)
|
|
127
150
|
-11, --oneonone Run one corresponding command on each host. (default: False)
|
|
128
151
|
-f FILE, --file FILE The file to be copied to the hosts. Use -f multiple times to copy multiple files
|
|
129
|
-
--file_sync
|
|
130
|
-
and destination will be the same in this mode. (default: False)
|
|
152
|
+
-fs, --file_sync Operate in file sync mode, sync path in <COMMANDS> from this machine to <HOSTS>. Treat --file <FILE> and
|
|
153
|
+
<COMMANDS> both as source as source and destination will be the same in this mode. (default: False)
|
|
131
154
|
--scp Use scp for copying files instead of rsync. Need to use this on windows. (default: False)
|
|
155
|
+
-gm, --gather_mode Gather files from the hosts instead of sending files to the hosts. Will send remote files specified in <FILE> to
|
|
156
|
+
local path specified in <COMMANDS> (default: False)
|
|
132
157
|
-t TIMEOUT, --timeout TIMEOUT
|
|
133
|
-
Timeout for each command in seconds (default:
|
|
158
|
+
Timeout for each command in seconds (default: 600 (disabled))
|
|
134
159
|
-r REPEAT, --repeat REPEAT
|
|
135
160
|
Repeat the command for a number of times (default: 1)
|
|
136
161
|
-i INTERVAL, --interval INTERVAL
|
|
137
162
|
Interval between repeats in seconds (default: 0)
|
|
138
163
|
--ipmi Use ipmitool to run the command. (default: False)
|
|
164
|
+
-mpre IPMI_INTERFACE_IP_PREFIX, --ipmi_interface_ip_prefix IPMI_INTERFACE_IP_PREFIX
|
|
165
|
+
The prefix of the IPMI interfaces (default: )
|
|
139
166
|
-pre INTERFACE_IP_PREFIX, --interface_ip_prefix INTERFACE_IP_PREFIX
|
|
140
167
|
The prefix of the for the interfaces (default: None)
|
|
141
|
-
-q,
|
|
168
|
+
-q, -nw, --nowatch, --quiet
|
|
169
|
+
Quiet mode, no curses watch, only print the output. (default: False)
|
|
142
170
|
-ww WINDOW_WIDTH, --window_width WINDOW_WIDTH
|
|
143
171
|
The minimum character length of the curses window. (default: 40)
|
|
144
172
|
-wh WINDOW_HEIGHT, --window_height WINDOW_HEIGHT
|
|
145
|
-
The minimum line height of the curses window. (default:
|
|
173
|
+
The minimum line height of the curses window. (default: 5)
|
|
146
174
|
-sw, --single_window Use a single window for all hosts. (default: False)
|
|
147
175
|
-eo, --error_only Only print the error output. (default: False)
|
|
148
|
-
-no, --
|
|
149
|
-
--no_env Do not load the environment variables. (default: False)
|
|
150
|
-
--env_file ENV_FILE The file to load the environment variables from. (default:
|
|
151
|
-
|
|
176
|
+
-no, --no_output Do not print the output. (default: False)
|
|
177
|
+
--no_env Do not load the command line environment variables. (default: False)
|
|
178
|
+
--env_file ENV_FILE The file to load the mssh file based environment variables from. ( Still work with --no_env ) (default:
|
|
179
|
+
/etc/profile.d/hosts.sh)
|
|
180
|
+
-m MAX_CONNECTIONS, --max_connections MAX_CONNECTIONS
|
|
152
181
|
Max number of connections to use (default: 4 * cpu_count)
|
|
153
182
|
-j, --json Output in json format. (default: False)
|
|
154
183
|
--success_hosts Output the hosts that succeeded in summary as wells. (default: False)
|
|
155
184
|
-g, --greppable Output in greppable format. (default: False)
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
-su, --skip_unreachable
|
|
186
|
+
Skip unreachable hosts while using --repeat. Note: Timedout Hosts are considered unreachable. Note: multiple
|
|
187
|
+
command sequence will still auto skip unreachable hosts. (default: False)
|
|
188
|
+
-sh SKIP_HOSTS, --skip_hosts SKIP_HOSTS
|
|
189
|
+
Skip the hosts in the list. (default: None)
|
|
190
|
+
--store_config_file Store / generate the default config file from command line argument and current config at
|
|
191
|
+
/etc/multiSSH3.config.json
|
|
192
|
+
--debug Print debug information
|
|
193
|
+
--copy-id Copy the ssh id to the hosts
|
|
162
194
|
-V, --version show program's version number and exit
|
|
163
195
|
```
|
|
164
196
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
multiSSH3.py,sha256=MdhvFCCWxQCZkjqFcsCp9nwiYiB1jKg1_YLu6BBrqZ4,96689
|
|
2
|
+
multiSSH3-4.99.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
multiSSH3-4.99.dist-info/METADATA,sha256=cLbJhPOIt81YaMyAn5iOocyPvEST-3RRFP-uafmZm30,17517
|
|
4
|
+
multiSSH3-4.99.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
|
5
|
+
multiSSH3-4.99.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
+
multiSSH3-4.99.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
+
multiSSH3-4.99.dist-info/RECORD,,
|
multiSSH3.py
CHANGED
|
@@ -30,7 +30,7 @@ except AttributeError:
|
|
|
30
30
|
# If neither is available, use a dummy decorator
|
|
31
31
|
def cache_decorator(func):
|
|
32
32
|
return func
|
|
33
|
-
version = '4.
|
|
33
|
+
version = '4.99'
|
|
34
34
|
VERSION = version
|
|
35
35
|
|
|
36
36
|
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
@@ -68,6 +68,9 @@ __build_in_default_config = {
|
|
|
68
68
|
'DEFAULT_HOSTS': 'all',
|
|
69
69
|
'DEFAULT_USERNAME': None,
|
|
70
70
|
'DEFAULT_PASSWORD': '',
|
|
71
|
+
'DEFAULT_IDENTITY_FILE': None,
|
|
72
|
+
'DEDAULT_SSH_KEY_SEARCH_PATH': '~/.ssh/',
|
|
73
|
+
'DEFAULT_USE_KEY': False,
|
|
71
74
|
'DEFAULT_EXTRA_ARGS': None,
|
|
72
75
|
'DEFAULT_ONE_ON_ONE': False,
|
|
73
76
|
'DEFAULT_SCP': False,
|
|
@@ -115,6 +118,7 @@ __build_in_default_config = {
|
|
|
115
118
|
'_rsyncPath': None,
|
|
116
119
|
'_bashPath': None,
|
|
117
120
|
'__ERROR_MESSAGES_TO_IGNORE_REGEX':None,
|
|
121
|
+
'__DEBUG_MODE': False,
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
AUTHOR = __configs_from_file.get('AUTHOR', __build_in_default_config['AUTHOR'])
|
|
@@ -124,6 +128,9 @@ DEFAULT_HOSTS = __configs_from_file.get('DEFAULT_HOSTS', __build_in_default_conf
|
|
|
124
128
|
DEFAULT_ENV_FILE = __configs_from_file.get('DEFAULT_ENV_FILE', __build_in_default_config['DEFAULT_ENV_FILE'])
|
|
125
129
|
DEFAULT_USERNAME = __configs_from_file.get('DEFAULT_USERNAME', __build_in_default_config['DEFAULT_USERNAME'])
|
|
126
130
|
DEFAULT_PASSWORD = __configs_from_file.get('DEFAULT_PASSWORD', __build_in_default_config['DEFAULT_PASSWORD'])
|
|
131
|
+
DEFAULT_IDENTITY_FILE = __configs_from_file.get('DEFAULT_IDENTITY_FILE', __build_in_default_config['DEFAULT_IDENTITY_FILE'])
|
|
132
|
+
DEDAULT_SSH_KEY_SEARCH_PATH = __configs_from_file.get('DEDAULT_SSH_KEY_SEARCH_PATH', __build_in_default_config['DEDAULT_SSH_KEY_SEARCH_PATH'])
|
|
133
|
+
DEFAULT_USE_KEY = __configs_from_file.get('DEFAULT_USE_KEY', __build_in_default_config['DEFAULT_USE_KEY'])
|
|
127
134
|
DEFAULT_EXTRA_ARGS = __configs_from_file.get('DEFAULT_EXTRA_ARGS', __build_in_default_config['DEFAULT_EXTRA_ARGS'])
|
|
128
135
|
DEFAULT_ONE_ON_ONE = __configs_from_file.get('DEFAULT_ONE_ON_ONE', __build_in_default_config['DEFAULT_ONE_ON_ONE'])
|
|
129
136
|
DEFAULT_SCP = __configs_from_file.get('DEFAULT_SCP', __build_in_default_config['DEFAULT_SCP'])
|
|
@@ -168,6 +175,8 @@ if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
|
168
175
|
else:
|
|
169
176
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
170
177
|
|
|
178
|
+
__DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config['__DEBUG_MODE'])
|
|
179
|
+
|
|
171
180
|
|
|
172
181
|
|
|
173
182
|
__global_suppress_printout = True
|
|
@@ -190,9 +199,7 @@ def get_i():
|
|
|
190
199
|
return __host_i_counter
|
|
191
200
|
|
|
192
201
|
class Host:
|
|
193
|
-
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False):
|
|
194
|
-
global __host_i_counter
|
|
195
|
-
global __host_i_lock
|
|
202
|
+
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None):
|
|
196
203
|
self.name = name # the name of the host (hostname or IP address)
|
|
197
204
|
self.command = command # the command to run on the host
|
|
198
205
|
self.returncode = None # the return code of the command
|
|
@@ -211,12 +218,13 @@ class Host:
|
|
|
211
218
|
# also store a globally unique integer i from 0
|
|
212
219
|
self.i = get_i()
|
|
213
220
|
self.uuid = uuid.uuid4()
|
|
221
|
+
self.identity_file = identity_file
|
|
214
222
|
|
|
215
223
|
def __iter__(self):
|
|
216
224
|
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
217
225
|
def __repr__(self):
|
|
218
226
|
# return the complete data structure
|
|
219
|
-
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}, i={self.i}, uuid={self.uuid})"
|
|
227
|
+
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}, i={self.i}, uuid={self.uuid}), identity_file={self.identity_file}"
|
|
220
228
|
def __str__(self):
|
|
221
229
|
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr})"
|
|
222
230
|
|
|
@@ -254,7 +262,7 @@ def check_path(program_name):
|
|
|
254
262
|
return True
|
|
255
263
|
return False
|
|
256
264
|
|
|
257
|
-
[check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash']]
|
|
265
|
+
[check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash','ssh-copy-id']]
|
|
258
266
|
|
|
259
267
|
|
|
260
268
|
|
|
@@ -675,12 +683,15 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
675
683
|
global __ERROR_MESSAGES_TO_IGNORE_REGEX
|
|
676
684
|
global __ipmiiInterfaceIPPrefix
|
|
677
685
|
global _binPaths
|
|
686
|
+
global __DEBUG_MODE
|
|
678
687
|
try:
|
|
679
|
-
|
|
680
|
-
|
|
688
|
+
localExtraArgs = []
|
|
689
|
+
|
|
681
690
|
if not SSH_STRICT_HOST_KEY_CHECKING:
|
|
682
|
-
|
|
683
|
-
|
|
691
|
+
localExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
|
|
692
|
+
if host.identity_file:
|
|
693
|
+
localExtraArgs += ['-i',host.identity_file]
|
|
694
|
+
rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(localExtraArgs)]
|
|
684
695
|
host.username = None
|
|
685
696
|
host.address = host.name
|
|
686
697
|
if '@' in host.name:
|
|
@@ -737,6 +748,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
737
748
|
formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}'] + extraargs + [host.command]
|
|
738
749
|
elif 'ssh' in _binPaths:
|
|
739
750
|
host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
|
|
751
|
+
if __DEBUG_MODE:
|
|
752
|
+
host.stderr.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
|
|
740
753
|
host.ipmi = False
|
|
741
754
|
host.interface_ip_prefix = None
|
|
742
755
|
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
@@ -754,6 +767,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
754
767
|
useScp = True
|
|
755
768
|
elif 'rsync' in _binPaths:
|
|
756
769
|
host.output.append('scp not found on the local machine! Trying to use rsync...')
|
|
770
|
+
if __DEBUG_MODE:
|
|
771
|
+
host.stderr.append('scp not found on the local machine! Trying to use rsync...')
|
|
757
772
|
useScp = False
|
|
758
773
|
else:
|
|
759
774
|
host.output.append('scp not found on the local machine! Please install scp or rsync to use file sync mode.')
|
|
@@ -764,6 +779,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
764
779
|
useScp = False
|
|
765
780
|
elif 'scp' in _binPaths:
|
|
766
781
|
host.output.append('rsync not found on the local machine! Trying to use scp...')
|
|
782
|
+
if __DEBUG_MODE:
|
|
783
|
+
host.stderr.append('rsync not found on the local machine! Trying to use scp...')
|
|
767
784
|
useScp = True
|
|
768
785
|
else:
|
|
769
786
|
host.output.append('rsync not found on the local machine! Please install rsync or scp to use file sync mode.')
|
|
@@ -775,16 +792,17 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
775
792
|
else:
|
|
776
793
|
fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
|
|
777
794
|
if useScp:
|
|
778
|
-
formatedCMD = [_binPaths['scp'],'-rpB'] +
|
|
795
|
+
formatedCMD = [_binPaths['scp'],'-rpB'] + localExtraArgs + extraargs +['--']+fileArgs
|
|
779
796
|
else:
|
|
780
|
-
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] +
|
|
797
|
+
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + rsyncLocalExtraArgs + extraargs +['--']+fileArgs
|
|
781
798
|
else:
|
|
782
|
-
formatedCMD = [_binPaths['ssh']] +
|
|
799
|
+
formatedCMD = [_binPaths['ssh']] + localExtraArgs + extraargs +['--']+ [host.resolvedName, host.command]
|
|
783
800
|
if passwds and 'sshpass' in _binPaths:
|
|
784
801
|
formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
|
|
785
802
|
elif passwds:
|
|
786
803
|
host.output.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
|
|
787
|
-
|
|
804
|
+
if __DEBUG_MODE:
|
|
805
|
+
host.stderr.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
|
|
788
806
|
host.output.append('Please provide password via live input or use ssh key authentication.')
|
|
789
807
|
# # try to send the password via __keyPressesIn
|
|
790
808
|
# __keyPressesIn[-1] = list(passwds) + ['\n']
|
|
@@ -802,6 +820,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
802
820
|
with sem:
|
|
803
821
|
try:
|
|
804
822
|
host.output.append('Running command: '+' '.join(formatedCMD))
|
|
823
|
+
if __DEBUG_MODE:
|
|
824
|
+
host.stderr.append('Running command: '+' '.join(formatedCMD))
|
|
805
825
|
#host.stdout = []
|
|
806
826
|
proc = subprocess.Popen(formatedCMD,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)
|
|
807
827
|
# create a thread to handle stdout
|
|
@@ -828,7 +848,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
828
848
|
|
|
829
849
|
proc.terminate()
|
|
830
850
|
break
|
|
831
|
-
elif time.time() - host.lastUpdateTime >
|
|
851
|
+
elif time.time() - host.lastUpdateTime > max(1, timeout // 2):
|
|
832
852
|
timeoutLine = f'Timeout in [{timeout - int(time.time() - host.lastUpdateTime)}] seconds!'
|
|
833
853
|
if host.output and not host.output[-1].strip().startswith(timeoutLine):
|
|
834
854
|
# remove last line if it is a countdown
|
|
@@ -890,6 +910,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
890
910
|
if host.ipmi and host.returncode != 0 and any(['Unable to establish IPMI' in line for line in host.stderr]):
|
|
891
911
|
host.stderr = []
|
|
892
912
|
host.output.append('IPMI connection failed! Trying SSH connection...')
|
|
913
|
+
if __DEBUG_MODE:
|
|
914
|
+
host.stderr.append('IPMI connection failed! Trying SSH connection...')
|
|
893
915
|
host.ipmi = False
|
|
894
916
|
host.interface_ip_prefix = None
|
|
895
917
|
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
@@ -899,6 +921,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
899
921
|
host.stderr = []
|
|
900
922
|
host.stdout = []
|
|
901
923
|
host.output.append('Rsync connection failed! Trying SCP connection...')
|
|
924
|
+
if __DEBUG_MODE:
|
|
925
|
+
host.stderr.append('Rsync connection failed! Trying SCP connection...')
|
|
902
926
|
host.scp = True
|
|
903
927
|
ssh_command(host,sem,timeout,passwds)
|
|
904
928
|
|
|
@@ -1393,6 +1417,8 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
1393
1417
|
# update the unavailable hosts and global unavailable hosts
|
|
1394
1418
|
if willUpdateUnreachableHosts:
|
|
1395
1419
|
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!'))])
|
|
1420
|
+
if __DEBUG_MODE:
|
|
1421
|
+
print(f'Unreachable hosts: {unavailableHosts}')
|
|
1396
1422
|
__globalUnavailableHosts.update(unavailableHosts)
|
|
1397
1423
|
# update the os environment variable if not _no_env
|
|
1398
1424
|
if not _no_env:
|
|
@@ -1432,12 +1458,13 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
1432
1458
|
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,
|
|
1433
1459
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1434
1460
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
|
|
1435
|
-
file_sync = False, error_only = DEFAULT_ERROR_ONLY,
|
|
1461
|
+
file_sync = False, error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
1436
1462
|
shortend = False) -> str:
|
|
1437
1463
|
argsList = []
|
|
1438
1464
|
if oneonone: argsList.append('--oneonone' if not shortend else '-11')
|
|
1439
1465
|
if timeout and timeout != DEFAULT_TIMEOUT: argsList.append(f'--timeout={timeout}' if not shortend else f'-t={timeout}')
|
|
1440
1466
|
if password and password != DEFAULT_PASSWORD: argsList.append(f'--password="{password}"' if not shortend else f'-p="{password}"')
|
|
1467
|
+
if identity_file and identity_file != DEFAULT_IDENTITY_FILE: argsList.append(f'--key="{identity_file}"' if not shortend else f'-k="{identity_file}"')
|
|
1441
1468
|
if nowatch: argsList.append('--nowatch' if not shortend else '-q')
|
|
1442
1469
|
if json: argsList.append('--json' if not shortend else '-j')
|
|
1443
1470
|
if max_connections and max_connections != DEFAULT_MAX_CONNECTIONS: argsList.append(f'--max_connections={max_connections}' if not shortend else f'-m={max_connections}')
|
|
@@ -1462,7 +1489,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
1462
1489
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1463
1490
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1464
1491
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1465
|
-
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,
|
|
1492
|
+
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
1466
1493
|
shortend = False):
|
|
1467
1494
|
hosts = hosts if type(hosts) == str else frozenset(hosts)
|
|
1468
1495
|
hostStr = formHostStr(hosts)
|
|
@@ -1471,7 +1498,8 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
1471
1498
|
nowatch = nowatch,json = json,max_connections=max_connections,
|
|
1472
1499
|
files = files,ipmi = ipmi,interface_ip_prefix = interface_ip_prefix,scp=scp,gather_mode = gather_mode,
|
|
1473
1500
|
username=username,extraargs=extraargs,skipUnreachable=skipUnreachable,no_env=no_env,
|
|
1474
|
-
greppable=greppable,skip_hosts = skip_hosts, file_sync = file_sync,error_only = error_only,
|
|
1501
|
+
greppable=greppable,skip_hosts = skip_hosts, file_sync = file_sync,error_only = error_only, identity_file = identity_file,
|
|
1502
|
+
shortend = shortend)
|
|
1475
1503
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
1476
1504
|
return f'multissh {argsStr} {hostStr} {commandStr}'
|
|
1477
1505
|
|
|
@@ -1481,7 +1509,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1481
1509
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1482
1510
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1483
1511
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1484
|
-
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,quiet = False):
|
|
1512
|
+
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,quiet = False,identity_file = DEFAULT_IDENTITY_FILE):
|
|
1485
1513
|
f'''
|
|
1486
1514
|
Run the command on the hosts, aka multissh. main function
|
|
1487
1515
|
|
|
@@ -1515,6 +1543,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1515
1543
|
file_sync (bool, optional): Whether to use file sync mode to sync directories. Defaults to {DEFAULT_FILE_SYNC}.
|
|
1516
1544
|
error_only (bool, optional): Whether to only print the error output. Defaults to {DEFAULT_ERROR_ONLY}.
|
|
1517
1545
|
quiet (bool, optional): Whether to suppress all verbose printout, added for compatibility, avoid using. Defaults to False.
|
|
1546
|
+
identity_file (str, optional): The identity file to use for the ssh connection. Defaults to {DEFAULT_IDENTITY_FILE}.
|
|
1518
1547
|
|
|
1519
1548
|
Returns:
|
|
1520
1549
|
list: A list of Host objects
|
|
@@ -1523,6 +1552,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1523
1552
|
global __global_suppress_printout
|
|
1524
1553
|
global _no_env
|
|
1525
1554
|
global _emo
|
|
1555
|
+
global __DEBUG_MODE
|
|
1526
1556
|
_emo = False
|
|
1527
1557
|
_no_env = no_env
|
|
1528
1558
|
if not no_env and '__multiSSH3_UNAVAILABLE_HOSTS' in os.environ:
|
|
@@ -1585,6 +1615,8 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1585
1615
|
skipHostStr[i] = userStr + host
|
|
1586
1616
|
skipHostStr = ','.join(skipHostStr)
|
|
1587
1617
|
targetHostsList = expand_hostnames(frozenset(hostStr.split(',')))
|
|
1618
|
+
if __DEBUG_MODE:
|
|
1619
|
+
eprint(f"Target hosts: {targetHostsList}")
|
|
1588
1620
|
skipHostsList = expand_hostnames(frozenset(skipHostStr.split(',')))
|
|
1589
1621
|
if skipHostsList:
|
|
1590
1622
|
eprint(f"Skipping hosts: {skipHostsList}")
|
|
@@ -1614,6 +1646,8 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1614
1646
|
files = []
|
|
1615
1647
|
else:
|
|
1616
1648
|
files = list(pathSet)
|
|
1649
|
+
if __DEBUG_MODE:
|
|
1650
|
+
eprint(f"Files: {files}")
|
|
1617
1651
|
if oneonone:
|
|
1618
1652
|
hosts = []
|
|
1619
1653
|
if len(commands) != len(targetHostsList) - len(skipHostsList):
|
|
@@ -1630,9 +1664,9 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1630
1664
|
continue
|
|
1631
1665
|
if host.strip() in skipHostsList: continue
|
|
1632
1666
|
if file_sync:
|
|
1633
|
-
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))
|
|
1667
|
+
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,identity_file=identity_file))
|
|
1634
1668
|
else:
|
|
1635
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1669
|
+
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode,identity_file=identity_file))
|
|
1636
1670
|
if not __global_suppress_printout:
|
|
1637
1671
|
eprint(f"Running command: {command} on host: {host}")
|
|
1638
1672
|
if not __global_suppress_printout: print('-'*80)
|
|
@@ -1656,7 +1690,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1656
1690
|
elif ipmi:
|
|
1657
1691
|
eprint(f"Error: ipmi mode is not supported in interactive mode")
|
|
1658
1692
|
else:
|
|
1659
|
-
hosts.append(Host(host.strip(), '', files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1693
|
+
hosts.append(Host(host.strip(), '', files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,identity_file=identity_file))
|
|
1660
1694
|
if not __global_suppress_printout:
|
|
1661
1695
|
eprint('-'*80)
|
|
1662
1696
|
eprint(f"Running in interactive mode on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
@@ -1674,9 +1708,9 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1674
1708
|
continue
|
|
1675
1709
|
if host.strip() in skipHostsList: continue
|
|
1676
1710
|
if file_sync:
|
|
1677
|
-
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))
|
|
1711
|
+
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,identity_file=identity_file))
|
|
1678
1712
|
else:
|
|
1679
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1713
|
+
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode,identity_file=identity_file))
|
|
1680
1714
|
if not __global_suppress_printout and len(commands) > 1:
|
|
1681
1715
|
eprint('-'*80)
|
|
1682
1716
|
eprint(f"Running command: {command} on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
@@ -1701,6 +1735,9 @@ def get_default_config(args):
|
|
|
1701
1735
|
'DEFAULT_HOSTS': args.hosts,
|
|
1702
1736
|
'DEFAULT_USERNAME': args.username,
|
|
1703
1737
|
'DEFAULT_PASSWORD': args.password,
|
|
1738
|
+
'DEFAULT_IDENTITY_FILE': args.key if args.key and not os.path.isdir(args.key) else DEFAULT_IDENTITY_FILE,
|
|
1739
|
+
'DEDAULT_SSH_KEY_SEARCH_PATH': args.key if args.key and os.path.isdir(args.key) else DEDAULT_SSH_KEY_SEARCH_PATH,
|
|
1740
|
+
'DEFAULT_USE_KEY': args.use_key,
|
|
1704
1741
|
'DEFAULT_EXTRA_ARGS': args.extraargs,
|
|
1705
1742
|
'DEFAULT_ONE_ON_ONE': args.oneonone,
|
|
1706
1743
|
'DEFAULT_SCP': args.scp,
|
|
@@ -1739,6 +1776,26 @@ def write_default_config(args,CONFIG_FILE,backup = True):
|
|
|
1739
1776
|
with open(CONFIG_FILE,'w') as f:
|
|
1740
1777
|
json.dump(__configs_from_file,f,indent=4)
|
|
1741
1778
|
|
|
1779
|
+
def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
|
|
1780
|
+
'''
|
|
1781
|
+
Find the ssh public key file
|
|
1782
|
+
|
|
1783
|
+
Args:
|
|
1784
|
+
searchPath (str, optional): The path to search. Defaults to DEDAULT_SSH_KEY_SEARCH_PATH.
|
|
1785
|
+
|
|
1786
|
+
Returns:
|
|
1787
|
+
str: The path to the ssh key file
|
|
1788
|
+
'''
|
|
1789
|
+
if searchPath:
|
|
1790
|
+
sshKeyPath = searchPath
|
|
1791
|
+
else:
|
|
1792
|
+
sshKeyPath ='~/.ssh'
|
|
1793
|
+
possibleSshKeyFiles = ['id_ed25519','id_ed25519_sk','id_ecdsa','id_ecdsa_sk','id_rsa','id_dsa']
|
|
1794
|
+
for sshKeyFile in possibleSshKeyFiles:
|
|
1795
|
+
if os.path.exists(os.path.expanduser(os.path.join(sshKeyPath,sshKeyFile))):
|
|
1796
|
+
return os.path.join(sshKeyPath,sshKeyFile)
|
|
1797
|
+
return None
|
|
1798
|
+
|
|
1742
1799
|
|
|
1743
1800
|
def main():
|
|
1744
1801
|
global _emo
|
|
@@ -1748,15 +1805,18 @@ def main():
|
|
|
1748
1805
|
global __ipmiiInterfaceIPPrefix
|
|
1749
1806
|
global _binPaths
|
|
1750
1807
|
global _env_file
|
|
1808
|
+
global __DEBUG_MODE
|
|
1751
1809
|
_emo = False
|
|
1752
1810
|
# We handle the signal
|
|
1753
1811
|
signal.signal(signal.SIGINT, signal_handler)
|
|
1754
1812
|
# We parse the arguments
|
|
1755
1813
|
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}')
|
|
1756
1814
|
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)
|
|
1757
|
-
parser.add_argument('commands', metavar='commands', type=str, nargs='
|
|
1815
|
+
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.')
|
|
1758
1816
|
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)
|
|
1759
1817
|
parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
|
|
1818
|
+
parser.add_argument('-k','--key','--identity',nargs='?', type=str,help=f'The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use {DEDAULT_SSH_KEY_SEARCH_PATH} (default: {DEFAULT_IDENTITY_FILE})',const=DEDAULT_SSH_KEY_SEARCH_PATH,default=DEFAULT_IDENTITY_FILE)
|
|
1819
|
+
parser.add_argument('-uk','--use_key', action='store_true', help=f'Attempt to use public key file to connect to the hosts. (default: {DEFAULT_USE_KEY})', default=DEFAULT_USE_KEY)
|
|
1760
1820
|
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)
|
|
1761
1821
|
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)
|
|
1762
1822
|
parser.add_argument("-f","--file", action='append', help="The file to be copied to the hosts. Use -f multiple times to copy multiple files")
|
|
@@ -1785,11 +1845,26 @@ def main():
|
|
|
1785
1845
|
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)
|
|
1786
1846
|
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)
|
|
1787
1847
|
parser.add_argument('--store_config_file', action='store_true', help=f'Store / generate the default config file from command line argument and current config at {CONFIG_FILE}')
|
|
1848
|
+
parser.add_argument('--debug', action='store_true', help='Print debug information')
|
|
1849
|
+
parser.add_argument('--copy-id', action='store_true', help='Copy the ssh id to the hosts')
|
|
1788
1850
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
1789
1851
|
|
|
1790
1852
|
# parser.add_argument('-u', '--user', metavar='user', type=str, nargs=1,
|
|
1791
1853
|
# help='the user to use to connect to the hosts')
|
|
1792
|
-
args = parser.parse_args()
|
|
1854
|
+
#args = parser.parse_args()
|
|
1855
|
+
|
|
1856
|
+
# if python version is 3.7 or higher, use parse_intermixed_args
|
|
1857
|
+
if sys.version_info >= (3,7):
|
|
1858
|
+
args = parser.parse_intermixed_args()
|
|
1859
|
+
else:
|
|
1860
|
+
# try to parse the arguments using parse_known_args
|
|
1861
|
+
args, unknown = parser.parse_known_args()
|
|
1862
|
+
# if there are unknown arguments, we will try to parse them again using parse_args
|
|
1863
|
+
if unknown:
|
|
1864
|
+
eprint(f"Warning: Unknown arguments, treating all as commands: {unknown}")
|
|
1865
|
+
args.commands += unknown
|
|
1866
|
+
|
|
1867
|
+
|
|
1793
1868
|
|
|
1794
1869
|
if args.store_config_file:
|
|
1795
1870
|
try:
|
|
@@ -1810,12 +1885,15 @@ def main():
|
|
|
1810
1885
|
eprint(f"Config file written to {CONFIG_FILE}")
|
|
1811
1886
|
except Exception as e:
|
|
1812
1887
|
eprint(f"Error while writing config file: {e}")
|
|
1888
|
+
import traceback
|
|
1889
|
+
eprint(traceback.format_exc())
|
|
1813
1890
|
if not args.commands:
|
|
1814
1891
|
with open(CONFIG_FILE,'r') as f:
|
|
1815
1892
|
eprint(f"Config file content: \n{f.read()}")
|
|
1816
1893
|
sys.exit(0)
|
|
1817
1894
|
|
|
1818
1895
|
_env_file = args.env_file
|
|
1896
|
+
__DEBUG_MODE = args.debug
|
|
1819
1897
|
# if there are more than 1 commands, and every command only consists of one word,
|
|
1820
1898
|
# we will ask the user to confirm if they want to run multiple commands or just one command.
|
|
1821
1899
|
if not args.file and len(args.commands) > 1 and all([len(command.split()) == 1 for command in args.commands]):
|
|
@@ -1831,7 +1909,35 @@ def main():
|
|
|
1831
1909
|
eprint(f"\nRunning multiple commands: {', '.join(args.commands)} on all hosts")
|
|
1832
1910
|
else:
|
|
1833
1911
|
sys.exit(0)
|
|
1834
|
-
|
|
1912
|
+
|
|
1913
|
+
if args.key or args.use_key:
|
|
1914
|
+
if not args.key:
|
|
1915
|
+
args.key = find_ssh_key_file()
|
|
1916
|
+
else:
|
|
1917
|
+
if os.path.isdir(os.path.expanduser(args.key)):
|
|
1918
|
+
args.key = find_ssh_key_file(args.key)
|
|
1919
|
+
elif not os.path.exists(args.key):
|
|
1920
|
+
eprint(f"Warning: Identity file {args.key} not found. Passing to ssh anyway. Proceed with caution.")
|
|
1921
|
+
|
|
1922
|
+
if args.copy_id:
|
|
1923
|
+
if 'ssh-copy-id' in _binPaths:
|
|
1924
|
+
# we will copy the id to the hosts
|
|
1925
|
+
for host in formHostStr(args.hosts).split(','):
|
|
1926
|
+
command = f"{_binPaths['ssh-copy-id']} "
|
|
1927
|
+
if args.key:
|
|
1928
|
+
command = f"{command}-i {args.key} "
|
|
1929
|
+
if args.username:
|
|
1930
|
+
command = f"{command} {args.username}@"
|
|
1931
|
+
command = f"{command}{host}"
|
|
1932
|
+
if args.password and 'sshpass' in _binPaths:
|
|
1933
|
+
command = f"{_binPaths['sshpass']} -p {args.password} {command}"
|
|
1934
|
+
eprint(f"> {command}")
|
|
1935
|
+
os.system(command)
|
|
1936
|
+
else:
|
|
1937
|
+
eprint(f"Warning: ssh-copy-id not found in {_binPaths} , skipping copy id to the hosts")
|
|
1938
|
+
if not args.commands:
|
|
1939
|
+
sys.exit(0)
|
|
1940
|
+
|
|
1835
1941
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
1836
1942
|
|
|
1837
1943
|
if not args.greppable and not args.json and not args.no_output:
|
|
@@ -1842,7 +1948,7 @@ def main():
|
|
|
1842
1948
|
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1843
1949
|
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,
|
|
1844
1950
|
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1845
|
-
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only))
|
|
1951
|
+
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only,identity_file=args.key))
|
|
1846
1952
|
if args.error_only:
|
|
1847
1953
|
__global_suppress_printout = True
|
|
1848
1954
|
|
|
@@ -1857,7 +1963,7 @@ def main():
|
|
|
1857
1963
|
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1858
1964
|
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,
|
|
1859
1965
|
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1860
|
-
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only)
|
|
1966
|
+
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only,identity_file=args.key)
|
|
1861
1967
|
#print('*'*80)
|
|
1862
1968
|
|
|
1863
1969
|
if not __global_suppress_printout: eprint('-'*80)
|
multiSSH3-4.97.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=WCp2vui9NMeviCPe39CVu9WY0-lOn3YEIzFGO8i8m7o,91417
|
|
2
|
-
multiSSH3-4.97.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
multiSSH3-4.97.dist-info/METADATA,sha256=ndgCxl2LHcQ3x88vqxJhTOd6ah7U46-Xva-Uj74oBzU,16043
|
|
4
|
-
multiSSH3-4.97.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
5
|
-
multiSSH3-4.97.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
-
multiSSH3-4.97.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
-
multiSSH3-4.97.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|