multiSSH3 4.98__py3-none-any.whl → 5.0__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.98.dist-info → multiSSH3-5.0.dist-info}/METADATA +62 -30
- multiSSH3-5.0.dist-info/RECORD +7 -0
- {multiSSH3-4.98.dist-info → multiSSH3-5.0.dist-info}/WHEEL +1 -1
- multiSSH3.py +139 -37
- multiSSH3-4.98.dist-info/RECORD +0 -7
- {multiSSH3-4.98.dist-info → multiSSH3-5.0.dist-info}/LICENSE +0 -0
- {multiSSH3-4.98.dist-info → multiSSH3-5.0.dist-info}/entry_points.txt +0 -0
- {multiSSH3-4.98.dist-info → multiSSH3-5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0
|
|
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=mNEOxE6IVnDu9eHdNfsmY_ZW765SFsf4fuelzG4hBDE,98229
|
|
2
|
+
multiSSH3-5.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
multiSSH3-5.0.dist-info/METADATA,sha256=iYZECVBpY6n8O-y92Z7aN0E8MuuiK4F91KcV8BjenAU,17516
|
|
4
|
+
multiSSH3-5.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
|
5
|
+
multiSSH3-5.0.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
+
multiSSH3-5.0.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
+
multiSSH3-5.0.dist-info/RECORD,,
|
multiSSH3.py
CHANGED
|
@@ -18,6 +18,7 @@ import glob
|
|
|
18
18
|
import shutil
|
|
19
19
|
import getpass
|
|
20
20
|
import uuid
|
|
21
|
+
import tempfile
|
|
21
22
|
|
|
22
23
|
try:
|
|
23
24
|
# Check if functiools.cache is available
|
|
@@ -30,7 +31,7 @@ except AttributeError:
|
|
|
30
31
|
# If neither is available, use a dummy decorator
|
|
31
32
|
def cache_decorator(func):
|
|
32
33
|
return func
|
|
33
|
-
version = '
|
|
34
|
+
version = '5.00'
|
|
34
35
|
VERSION = version
|
|
35
36
|
|
|
36
37
|
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
@@ -68,6 +69,9 @@ __build_in_default_config = {
|
|
|
68
69
|
'DEFAULT_HOSTS': 'all',
|
|
69
70
|
'DEFAULT_USERNAME': None,
|
|
70
71
|
'DEFAULT_PASSWORD': '',
|
|
72
|
+
'DEFAULT_IDENTITY_FILE': None,
|
|
73
|
+
'DEDAULT_SSH_KEY_SEARCH_PATH': '~/.ssh/',
|
|
74
|
+
'DEFAULT_USE_KEY': False,
|
|
71
75
|
'DEFAULT_EXTRA_ARGS': None,
|
|
72
76
|
'DEFAULT_ONE_ON_ONE': False,
|
|
73
77
|
'DEFAULT_SCP': False,
|
|
@@ -91,7 +95,7 @@ __build_in_default_config = {
|
|
|
91
95
|
'DEFAULT_JSON_MODE': False,
|
|
92
96
|
'DEFAULT_PRINT_SUCCESS_HOSTS': False,
|
|
93
97
|
'DEFAULT_GREPPABLE_MODE': False,
|
|
94
|
-
'DEFAULT_SKIP_UNREACHABLE':
|
|
98
|
+
'DEFAULT_SKIP_UNREACHABLE': True,
|
|
95
99
|
'DEFAULT_SKIP_HOSTS': '',
|
|
96
100
|
'SSH_STRICT_HOST_KEY_CHECKING': False,
|
|
97
101
|
'ERROR_MESSAGES_TO_IGNORE': [
|
|
@@ -125,6 +129,9 @@ DEFAULT_HOSTS = __configs_from_file.get('DEFAULT_HOSTS', __build_in_default_conf
|
|
|
125
129
|
DEFAULT_ENV_FILE = __configs_from_file.get('DEFAULT_ENV_FILE', __build_in_default_config['DEFAULT_ENV_FILE'])
|
|
126
130
|
DEFAULT_USERNAME = __configs_from_file.get('DEFAULT_USERNAME', __build_in_default_config['DEFAULT_USERNAME'])
|
|
127
131
|
DEFAULT_PASSWORD = __configs_from_file.get('DEFAULT_PASSWORD', __build_in_default_config['DEFAULT_PASSWORD'])
|
|
132
|
+
DEFAULT_IDENTITY_FILE = __configs_from_file.get('DEFAULT_IDENTITY_FILE', __build_in_default_config['DEFAULT_IDENTITY_FILE'])
|
|
133
|
+
DEDAULT_SSH_KEY_SEARCH_PATH = __configs_from_file.get('DEDAULT_SSH_KEY_SEARCH_PATH', __build_in_default_config['DEDAULT_SSH_KEY_SEARCH_PATH'])
|
|
134
|
+
DEFAULT_USE_KEY = __configs_from_file.get('DEFAULT_USE_KEY', __build_in_default_config['DEFAULT_USE_KEY'])
|
|
128
135
|
DEFAULT_EXTRA_ARGS = __configs_from_file.get('DEFAULT_EXTRA_ARGS', __build_in_default_config['DEFAULT_EXTRA_ARGS'])
|
|
129
136
|
DEFAULT_ONE_ON_ONE = __configs_from_file.get('DEFAULT_ONE_ON_ONE', __build_in_default_config['DEFAULT_ONE_ON_ONE'])
|
|
130
137
|
DEFAULT_SCP = __configs_from_file.get('DEFAULT_SCP', __build_in_default_config['DEFAULT_SCP'])
|
|
@@ -173,7 +180,7 @@ __DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config
|
|
|
173
180
|
|
|
174
181
|
|
|
175
182
|
|
|
176
|
-
__global_suppress_printout =
|
|
183
|
+
__global_suppress_printout = False
|
|
177
184
|
|
|
178
185
|
__mainReturnCode = 0
|
|
179
186
|
__failedHosts = set()
|
|
@@ -193,7 +200,7 @@ def get_i():
|
|
|
193
200
|
return __host_i_counter
|
|
194
201
|
|
|
195
202
|
class Host:
|
|
196
|
-
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False):
|
|
203
|
+
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None):
|
|
197
204
|
self.name = name # the name of the host (hostname or IP address)
|
|
198
205
|
self.command = command # the command to run on the host
|
|
199
206
|
self.returncode = None # the return code of the command
|
|
@@ -212,12 +219,13 @@ class Host:
|
|
|
212
219
|
# also store a globally unique integer i from 0
|
|
213
220
|
self.i = get_i()
|
|
214
221
|
self.uuid = uuid.uuid4()
|
|
222
|
+
self.identity_file = identity_file
|
|
215
223
|
|
|
216
224
|
def __iter__(self):
|
|
217
225
|
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
218
226
|
def __repr__(self):
|
|
219
227
|
# return the complete data structure
|
|
220
|
-
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})"
|
|
228
|
+
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}"
|
|
221
229
|
def __str__(self):
|
|
222
230
|
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr})"
|
|
223
231
|
|
|
@@ -255,7 +263,7 @@ def check_path(program_name):
|
|
|
255
263
|
return True
|
|
256
264
|
return False
|
|
257
265
|
|
|
258
|
-
[check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash']]
|
|
266
|
+
[check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash','ssh-copy-id']]
|
|
259
267
|
|
|
260
268
|
|
|
261
269
|
|
|
@@ -678,11 +686,13 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
678
686
|
global _binPaths
|
|
679
687
|
global __DEBUG_MODE
|
|
680
688
|
try:
|
|
681
|
-
|
|
682
|
-
|
|
689
|
+
localExtraArgs = []
|
|
690
|
+
|
|
683
691
|
if not SSH_STRICT_HOST_KEY_CHECKING:
|
|
684
|
-
|
|
685
|
-
|
|
692
|
+
localExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
|
|
693
|
+
if host.identity_file:
|
|
694
|
+
localExtraArgs += ['-i',host.identity_file]
|
|
695
|
+
rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(localExtraArgs)]
|
|
686
696
|
host.username = None
|
|
687
697
|
host.address = host.name
|
|
688
698
|
if '@' in host.name:
|
|
@@ -783,11 +793,11 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
783
793
|
else:
|
|
784
794
|
fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
|
|
785
795
|
if useScp:
|
|
786
|
-
formatedCMD = [_binPaths['scp'],'-rpB'] +
|
|
796
|
+
formatedCMD = [_binPaths['scp'],'-rpB'] + localExtraArgs + extraargs +['--']+fileArgs
|
|
787
797
|
else:
|
|
788
|
-
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] +
|
|
798
|
+
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + rsyncLocalExtraArgs + extraargs +['--']+fileArgs
|
|
789
799
|
else:
|
|
790
|
-
formatedCMD = [_binPaths['ssh']] +
|
|
800
|
+
formatedCMD = [_binPaths['ssh']] + localExtraArgs + extraargs +['--']+ [host.resolvedName, host.command]
|
|
791
801
|
if passwds and 'sshpass' in _binPaths:
|
|
792
802
|
formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
|
|
793
803
|
elif passwds:
|
|
@@ -839,7 +849,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
839
849
|
|
|
840
850
|
proc.terminate()
|
|
841
851
|
break
|
|
842
|
-
elif time.time() - host.lastUpdateTime >
|
|
852
|
+
elif time.time() - host.lastUpdateTime > max(1, timeout // 2):
|
|
843
853
|
timeoutLine = f'Timeout in [{timeout - int(time.time() - host.lastUpdateTime)}] seconds!'
|
|
844
854
|
if host.output and not host.output[-1].strip().startswith(timeoutLine):
|
|
845
855
|
# remove last line if it is a countdown
|
|
@@ -1293,9 +1303,10 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
1293
1303
|
outputs[hostPrintOut] = [host['name']]
|
|
1294
1304
|
else:
|
|
1295
1305
|
outputs[hostPrintOut].append(host['name'])
|
|
1296
|
-
rtnStr = ''
|
|
1306
|
+
rtnStr = '*'*80+'\n'
|
|
1297
1307
|
for output, hosts in outputs.items():
|
|
1298
1308
|
rtnStr += f"{','.join(hosts)}{output}\n"
|
|
1309
|
+
rtnStr += '*'*80+'\n'
|
|
1299
1310
|
if __keyPressesIn[-1]:
|
|
1300
1311
|
CMDsOut = [''.join(cmd).encode('unicode_escape').decode().replace('\\n', '↵') for cmd in __keyPressesIn if cmd]
|
|
1301
1312
|
rtnStr += 'User Inputs: '+ '\nUser Inputs: '.join(CMDsOut)
|
|
@@ -1411,9 +1422,25 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
1411
1422
|
if __DEBUG_MODE:
|
|
1412
1423
|
print(f'Unreachable hosts: {unavailableHosts}')
|
|
1413
1424
|
__globalUnavailableHosts.update(unavailableHosts)
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1425
|
+
|
|
1426
|
+
# os.environ['__multiSSH3_UNAVAILABLE_HOSTS'] = ','.join(unavailableHosts)
|
|
1427
|
+
# create a temporary file to store the unavailable hosts
|
|
1428
|
+
try:
|
|
1429
|
+
# check for the old content, only update if the new content is different
|
|
1430
|
+
if not os.path.exists(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS')):
|
|
1431
|
+
with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS'),'w') as f:
|
|
1432
|
+
f.write(','.join(unavailableHosts))
|
|
1433
|
+
else:
|
|
1434
|
+
try:
|
|
1435
|
+
with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS'),'r') as f:
|
|
1436
|
+
oldSet = set(f.read().strip().split(','))
|
|
1437
|
+
except:
|
|
1438
|
+
oldSet = None
|
|
1439
|
+
if not oldSet or set(oldSet) != unavailableHosts:
|
|
1440
|
+
with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS'),'w') as f:
|
|
1441
|
+
f.write(','.join(unavailableHosts))
|
|
1442
|
+
except Exception as e:
|
|
1443
|
+
eprint(f'Error writing to temporary file: {e}')
|
|
1417
1444
|
|
|
1418
1445
|
# print the output, if the output of multiple hosts are the same, we aggragate them
|
|
1419
1446
|
if not called:
|
|
@@ -1449,12 +1476,13 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
1449
1476
|
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,
|
|
1450
1477
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1451
1478
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
|
|
1452
|
-
file_sync = False, error_only = DEFAULT_ERROR_ONLY,
|
|
1479
|
+
file_sync = False, error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
1453
1480
|
shortend = False) -> str:
|
|
1454
1481
|
argsList = []
|
|
1455
1482
|
if oneonone: argsList.append('--oneonone' if not shortend else '-11')
|
|
1456
1483
|
if timeout and timeout != DEFAULT_TIMEOUT: argsList.append(f'--timeout={timeout}' if not shortend else f'-t={timeout}')
|
|
1457
1484
|
if password and password != DEFAULT_PASSWORD: argsList.append(f'--password="{password}"' if not shortend else f'-p="{password}"')
|
|
1485
|
+
if identity_file and identity_file != DEFAULT_IDENTITY_FILE: argsList.append(f'--key="{identity_file}"' if not shortend else f'-k="{identity_file}"')
|
|
1458
1486
|
if nowatch: argsList.append('--nowatch' if not shortend else '-q')
|
|
1459
1487
|
if json: argsList.append('--json' if not shortend else '-j')
|
|
1460
1488
|
if max_connections and max_connections != DEFAULT_MAX_CONNECTIONS: argsList.append(f'--max_connections={max_connections}' if not shortend else f'-m={max_connections}')
|
|
@@ -1479,7 +1507,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
1479
1507
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1480
1508
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1481
1509
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1482
|
-
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,
|
|
1510
|
+
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
1483
1511
|
shortend = False):
|
|
1484
1512
|
hosts = hosts if type(hosts) == str else frozenset(hosts)
|
|
1485
1513
|
hostStr = formHostStr(hosts)
|
|
@@ -1488,7 +1516,8 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
1488
1516
|
nowatch = nowatch,json = json,max_connections=max_connections,
|
|
1489
1517
|
files = files,ipmi = ipmi,interface_ip_prefix = interface_ip_prefix,scp=scp,gather_mode = gather_mode,
|
|
1490
1518
|
username=username,extraargs=extraargs,skipUnreachable=skipUnreachable,no_env=no_env,
|
|
1491
|
-
greppable=greppable,skip_hosts = skip_hosts, file_sync = file_sync,error_only = error_only,
|
|
1519
|
+
greppable=greppable,skip_hosts = skip_hosts, file_sync = file_sync,error_only = error_only, identity_file = identity_file,
|
|
1520
|
+
shortend = shortend)
|
|
1492
1521
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
1493
1522
|
return f'multissh {argsStr} {hostStr} {commandStr}'
|
|
1494
1523
|
|
|
@@ -1498,7 +1527,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1498
1527
|
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1499
1528
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1500
1529
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1501
|
-
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,quiet = False):
|
|
1530
|
+
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY,quiet = False,identity_file = DEFAULT_IDENTITY_FILE):
|
|
1502
1531
|
f'''
|
|
1503
1532
|
Run the command on the hosts, aka multissh. main function
|
|
1504
1533
|
|
|
@@ -1532,6 +1561,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1532
1561
|
file_sync (bool, optional): Whether to use file sync mode to sync directories. Defaults to {DEFAULT_FILE_SYNC}.
|
|
1533
1562
|
error_only (bool, optional): Whether to only print the error output. Defaults to {DEFAULT_ERROR_ONLY}.
|
|
1534
1563
|
quiet (bool, optional): Whether to suppress all verbose printout, added for compatibility, avoid using. Defaults to False.
|
|
1564
|
+
identity_file (str, optional): The identity file to use for the ssh connection. Defaults to {DEFAULT_IDENTITY_FILE}.
|
|
1535
1565
|
|
|
1536
1566
|
Returns:
|
|
1537
1567
|
list: A list of Host objects
|
|
@@ -1543,10 +1573,26 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1543
1573
|
global __DEBUG_MODE
|
|
1544
1574
|
_emo = False
|
|
1545
1575
|
_no_env = no_env
|
|
1546
|
-
if
|
|
1547
|
-
|
|
1576
|
+
if os.path.exists(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS')):
|
|
1577
|
+
if timeout <= 0:
|
|
1578
|
+
checkTime = DEFAULT_TIMEOUT
|
|
1579
|
+
else:
|
|
1580
|
+
checkTime = timeout
|
|
1581
|
+
if checkTime <= 0:
|
|
1582
|
+
checkTime = 60
|
|
1583
|
+
try:
|
|
1584
|
+
if 0 < time.time() - os.path.getmtime(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS')) < checkTime:
|
|
1585
|
+
if not __global_suppress_printout:
|
|
1586
|
+
eprint(f"Reading unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS')}")
|
|
1587
|
+
with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS'),'r') as f:
|
|
1588
|
+
__globalUnavailableHosts.update(f.read().strip().split(','))
|
|
1589
|
+
if __DEBUG_MODE:
|
|
1590
|
+
eprint(f"Unavailable hosts: {__globalUnavailableHosts}")
|
|
1591
|
+
except Exception as e:
|
|
1592
|
+
eprint(f"Warning: Unable to read the unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS')}")
|
|
1593
|
+
eprint(str(e))
|
|
1548
1594
|
elif '__multiSSH3_UNAVAILABLE_HOSTS' in readEnvFromFile():
|
|
1549
|
-
__globalUnavailableHosts
|
|
1595
|
+
__globalUnavailableHosts.update(readEnvFromFile()['__multiSSH3_UNAVAILABLE_HOSTS'].split(','))
|
|
1550
1596
|
if not max_connections:
|
|
1551
1597
|
max_connections = 4 * os.cpu_count()
|
|
1552
1598
|
elif max_connections == 0:
|
|
@@ -1652,9 +1698,9 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1652
1698
|
continue
|
|
1653
1699
|
if host.strip() in skipHostsList: continue
|
|
1654
1700
|
if file_sync:
|
|
1655
|
-
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))
|
|
1701
|
+
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))
|
|
1656
1702
|
else:
|
|
1657
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1703
|
+
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))
|
|
1658
1704
|
if not __global_suppress_printout:
|
|
1659
1705
|
eprint(f"Running command: {command} on host: {host}")
|
|
1660
1706
|
if not __global_suppress_printout: print('-'*80)
|
|
@@ -1678,7 +1724,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1678
1724
|
elif ipmi:
|
|
1679
1725
|
eprint(f"Error: ipmi mode is not supported in interactive mode")
|
|
1680
1726
|
else:
|
|
1681
|
-
hosts.append(Host(host.strip(), '', files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1727
|
+
hosts.append(Host(host.strip(), '', files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,identity_file=identity_file))
|
|
1682
1728
|
if not __global_suppress_printout:
|
|
1683
1729
|
eprint('-'*80)
|
|
1684
1730
|
eprint(f"Running in interactive mode on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
@@ -1696,9 +1742,9 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
1696
1742
|
continue
|
|
1697
1743
|
if host.strip() in skipHostsList: continue
|
|
1698
1744
|
if file_sync:
|
|
1699
|
-
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))
|
|
1745
|
+
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))
|
|
1700
1746
|
else:
|
|
1701
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1747
|
+
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))
|
|
1702
1748
|
if not __global_suppress_printout and len(commands) > 1:
|
|
1703
1749
|
eprint('-'*80)
|
|
1704
1750
|
eprint(f"Running command: {command} on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
@@ -1723,6 +1769,9 @@ def get_default_config(args):
|
|
|
1723
1769
|
'DEFAULT_HOSTS': args.hosts,
|
|
1724
1770
|
'DEFAULT_USERNAME': args.username,
|
|
1725
1771
|
'DEFAULT_PASSWORD': args.password,
|
|
1772
|
+
'DEFAULT_IDENTITY_FILE': args.key if args.key and not os.path.isdir(args.key) else DEFAULT_IDENTITY_FILE,
|
|
1773
|
+
'DEDAULT_SSH_KEY_SEARCH_PATH': args.key if args.key and os.path.isdir(args.key) else DEDAULT_SSH_KEY_SEARCH_PATH,
|
|
1774
|
+
'DEFAULT_USE_KEY': args.use_key,
|
|
1726
1775
|
'DEFAULT_EXTRA_ARGS': args.extraargs,
|
|
1727
1776
|
'DEFAULT_ONE_ON_ONE': args.oneonone,
|
|
1728
1777
|
'DEFAULT_SCP': args.scp,
|
|
@@ -1761,6 +1810,26 @@ def write_default_config(args,CONFIG_FILE,backup = True):
|
|
|
1761
1810
|
with open(CONFIG_FILE,'w') as f:
|
|
1762
1811
|
json.dump(__configs_from_file,f,indent=4)
|
|
1763
1812
|
|
|
1813
|
+
def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
|
|
1814
|
+
'''
|
|
1815
|
+
Find the ssh public key file
|
|
1816
|
+
|
|
1817
|
+
Args:
|
|
1818
|
+
searchPath (str, optional): The path to search. Defaults to DEDAULT_SSH_KEY_SEARCH_PATH.
|
|
1819
|
+
|
|
1820
|
+
Returns:
|
|
1821
|
+
str: The path to the ssh key file
|
|
1822
|
+
'''
|
|
1823
|
+
if searchPath:
|
|
1824
|
+
sshKeyPath = searchPath
|
|
1825
|
+
else:
|
|
1826
|
+
sshKeyPath ='~/.ssh'
|
|
1827
|
+
possibleSshKeyFiles = ['id_ed25519','id_ed25519_sk','id_ecdsa','id_ecdsa_sk','id_rsa','id_dsa']
|
|
1828
|
+
for sshKeyFile in possibleSshKeyFiles:
|
|
1829
|
+
if os.path.exists(os.path.expanduser(os.path.join(sshKeyPath,sshKeyFile))):
|
|
1830
|
+
return os.path.join(sshKeyPath,sshKeyFile)
|
|
1831
|
+
return None
|
|
1832
|
+
|
|
1764
1833
|
|
|
1765
1834
|
def main():
|
|
1766
1835
|
global _emo
|
|
@@ -1780,6 +1849,8 @@ def main():
|
|
|
1780
1849
|
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.')
|
|
1781
1850
|
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)
|
|
1782
1851
|
parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
|
|
1852
|
+
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)
|
|
1853
|
+
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)
|
|
1783
1854
|
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)
|
|
1784
1855
|
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)
|
|
1785
1856
|
parser.add_argument("-f","--file", action='append', help="The file to be copied to the hosts. Use -f multiple times to copy multiple files")
|
|
@@ -1805,7 +1876,7 @@ def main():
|
|
|
1805
1876
|
parser.add_argument("-j","--json", action='store_true', help=F"Output in json format. (default: {DEFAULT_JSON_MODE})", default=DEFAULT_JSON_MODE)
|
|
1806
1877
|
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)
|
|
1807
1878
|
parser.add_argument("-g","--greppable", action='store_true', help=f"Output in greppable format. (default: {DEFAULT_GREPPABLE_MODE})", default=DEFAULT_GREPPABLE_MODE)
|
|
1808
|
-
parser.add_argument("-su","--skip_unreachable", action='store_true', help=f"Skip unreachable hosts
|
|
1879
|
+
parser.add_argument("-su","--skip_unreachable", action='store_true', help=f"Skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {DEFAULT_SKIP_UNREACHABLE})", default=DEFAULT_SKIP_UNREACHABLE)
|
|
1809
1880
|
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)
|
|
1810
1881
|
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}')
|
|
1811
1882
|
parser.add_argument('--debug', action='store_true', help='Print debug information')
|
|
@@ -1848,6 +1919,8 @@ def main():
|
|
|
1848
1919
|
eprint(f"Config file written to {CONFIG_FILE}")
|
|
1849
1920
|
except Exception as e:
|
|
1850
1921
|
eprint(f"Error while writing config file: {e}")
|
|
1922
|
+
import traceback
|
|
1923
|
+
eprint(traceback.format_exc())
|
|
1851
1924
|
if not args.commands:
|
|
1852
1925
|
with open(CONFIG_FILE,'r') as f:
|
|
1853
1926
|
eprint(f"Config file content: \n{f.read()}")
|
|
@@ -1870,18 +1943,47 @@ def main():
|
|
|
1870
1943
|
eprint(f"\nRunning multiple commands: {', '.join(args.commands)} on all hosts")
|
|
1871
1944
|
else:
|
|
1872
1945
|
sys.exit(0)
|
|
1873
|
-
|
|
1946
|
+
|
|
1947
|
+
if args.key or args.use_key:
|
|
1948
|
+
if not args.key:
|
|
1949
|
+
args.key = find_ssh_key_file()
|
|
1950
|
+
else:
|
|
1951
|
+
if os.path.isdir(os.path.expanduser(args.key)):
|
|
1952
|
+
args.key = find_ssh_key_file(args.key)
|
|
1953
|
+
elif not os.path.exists(args.key):
|
|
1954
|
+
eprint(f"Warning: Identity file {args.key} not found. Passing to ssh anyway. Proceed with caution.")
|
|
1955
|
+
|
|
1956
|
+
if args.copy_id:
|
|
1957
|
+
if 'ssh-copy-id' in _binPaths:
|
|
1958
|
+
# we will copy the id to the hosts
|
|
1959
|
+
for host in formHostStr(args.hosts).split(','):
|
|
1960
|
+
command = f"{_binPaths['ssh-copy-id']} "
|
|
1961
|
+
if args.key:
|
|
1962
|
+
command = f"{command}-i {args.key} "
|
|
1963
|
+
if args.username:
|
|
1964
|
+
command = f"{command} {args.username}@"
|
|
1965
|
+
command = f"{command}{host}"
|
|
1966
|
+
if args.password and 'sshpass' in _binPaths:
|
|
1967
|
+
command = f"{_binPaths['sshpass']} -p {args.password} {command}"
|
|
1968
|
+
eprint(f"> {command}")
|
|
1969
|
+
os.system(command)
|
|
1970
|
+
else:
|
|
1971
|
+
eprint(f"Warning: ssh-copy-id not found in {_binPaths} , skipping copy id to the hosts")
|
|
1972
|
+
if not args.commands:
|
|
1973
|
+
sys.exit(0)
|
|
1974
|
+
|
|
1874
1975
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
1875
1976
|
|
|
1876
|
-
if
|
|
1877
|
-
__global_suppress_printout =
|
|
1977
|
+
if args.no_output:
|
|
1978
|
+
__global_suppress_printout = True
|
|
1878
1979
|
|
|
1879
1980
|
if not __global_suppress_printout:
|
|
1880
|
-
|
|
1981
|
+
cmdStr = getStrCommand(args.hosts,args.commands,oneonone=args.oneonone,timeout=args.timeout,password=args.password,
|
|
1881
1982
|
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1882
1983
|
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,
|
|
1883
1984
|
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1884
|
-
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only)
|
|
1985
|
+
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)
|
|
1986
|
+
eprint('> ' + cmdStr)
|
|
1885
1987
|
if args.error_only:
|
|
1886
1988
|
__global_suppress_printout = True
|
|
1887
1989
|
|
|
@@ -1896,7 +1998,7 @@ def main():
|
|
|
1896
1998
|
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1897
1999
|
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,
|
|
1898
2000
|
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1899
|
-
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only)
|
|
2001
|
+
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)
|
|
1900
2002
|
#print('*'*80)
|
|
1901
2003
|
|
|
1902
2004
|
if not __global_suppress_printout: eprint('-'*80)
|
multiSSH3-4.98.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=Dm7lML6_oR8yedBdlZCsR-J7Et2Xh5r149y1V666Avw,93039
|
|
2
|
-
multiSSH3-4.98.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
multiSSH3-4.98.dist-info/METADATA,sha256=hkcvqm3j6w0i24rVinCJMhoxax1btQbHoCaqG-SnJx0,16043
|
|
4
|
-
multiSSH3-4.98.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
5
|
-
multiSSH3-4.98.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
-
multiSSH3-4.98.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
-
multiSSH3-4.98.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|