multiSSH3 5.24__py3-none-any.whl → 5.27__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-5.24.dist-info → multiSSH3-5.27.dist-info}/METADATA +18 -10
- multiSSH3-5.27.dist-info/RECORD +7 -0
- multiSSH3.py +76 -25
- multiSSH3-5.24.dist-info/RECORD +0 -7
- {multiSSH3-5.24.dist-info → multiSSH3-5.27.dist-info}/LICENSE +0 -0
- {multiSSH3-5.24.dist-info → multiSSH3-5.27.dist-info}/WHEEL +0 -0
- {multiSSH3-5.24.dist-info → multiSSH3-5.27.dist-info}/entry_points.txt +0 -0
- {multiSSH3-5.24.dist-info → multiSSH3-5.27.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.27
|
|
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
|
|
@@ -96,7 +96,7 @@ An example .ssh/config:
|
|
|
96
96
|
Host *
|
|
97
97
|
StrictHostKeyChecking no
|
|
98
98
|
ControlMaster auto
|
|
99
|
-
ControlPath /
|
|
99
|
+
ControlPath /tmp/%u_ssh_sockets_%r@%h-%p
|
|
100
100
|
ControlPersist 3600
|
|
101
101
|
```
|
|
102
102
|
|
|
@@ -124,10 +124,10 @@ While leaving minimum 40 characters / 1 line for each host display by default. Y
|
|
|
124
124
|
Use ```mssh --help``` for more info.
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
usage: mssh [-h] [-u USERNAME] [-p PASSWORD] [-ea EXTRAARGS] [-11] [-f FILE] [-fs] [--scp] [-gm] [-t TIMEOUT] [-r REPEAT]
|
|
128
|
-
[--ipmi] [-mpre IPMI_INTERFACE_IP_PREFIX] [-pre INTERFACE_IP_PREFIX] [-q] [-ww WINDOW_WIDTH] [-wh WINDOW_HEIGHT]
|
|
129
|
-
[-no] [--no_env] [--env_file ENV_FILE] [-m MAX_CONNECTIONS] [-j] [--success_hosts] [-g] [-su
|
|
130
|
-
[--store_config_file] [--debug] [
|
|
127
|
+
usage: mssh [-h] [-u USERNAME] [-p PASSWORD] [-k [KEY]] [-uk] [-ea EXTRAARGS] [-11] [-f FILE] [-fs] [--scp] [-gm] [-t TIMEOUT] [-r REPEAT]
|
|
128
|
+
[-i INTERVAL] [--ipmi] [-mpre IPMI_INTERFACE_IP_PREFIX] [-pre INTERFACE_IP_PREFIX] [-q] [-ww WINDOW_WIDTH] [-wh WINDOW_HEIGHT]
|
|
129
|
+
[-sw] [-eo] [-no] [--no_env] [--env_file ENV_FILE] [-m MAX_CONNECTIONS] [-j] [--success_hosts] [-g] [-su | -nsu]
|
|
130
|
+
[-sh SKIP_HOSTS] [--store_config_file] [--debug] [-ci] [-V]
|
|
131
131
|
[hosts] [commands ...]
|
|
132
132
|
|
|
133
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
|
|
@@ -144,6 +144,10 @@ options:
|
|
|
144
144
|
(default: None)
|
|
145
145
|
-p PASSWORD, --password PASSWORD
|
|
146
146
|
The password to use to connect to the hosts, (default: )
|
|
147
|
+
-k [KEY], --key [KEY], --identity [KEY]
|
|
148
|
+
The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a
|
|
149
|
+
key. Use option without value to use ~/.ssh/ (default: None)
|
|
150
|
+
-uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
|
|
147
151
|
-ea EXTRAARGS, --extraargs EXTRAARGS
|
|
148
152
|
Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.Use "=" ! Ex.
|
|
149
153
|
-ea="--delete" (default: None)
|
|
@@ -181,16 +185,20 @@ options:
|
|
|
181
185
|
Max number of connections to use (default: 4 * cpu_count)
|
|
182
186
|
-j, --json Output in json format. (default: False)
|
|
183
187
|
--success_hosts Output the hosts that succeeded in summary as wells. (default: False)
|
|
184
|
-
-g, --greppable
|
|
188
|
+
-g, --greppable, --table
|
|
189
|
+
Output in greppable format. (default: False)
|
|
185
190
|
-su, --skip_unreachable
|
|
186
|
-
Skip unreachable hosts
|
|
187
|
-
|
|
191
|
+
Skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will
|
|
192
|
+
still auto skip unreachable hosts. (default: False)
|
|
193
|
+
-nsu, --no_skip_unreachable
|
|
194
|
+
Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence
|
|
195
|
+
will still auto skip unreachable hosts. (default: True)
|
|
188
196
|
-sh SKIP_HOSTS, --skip_hosts SKIP_HOSTS
|
|
189
197
|
Skip the hosts in the list. (default: None)
|
|
190
198
|
--store_config_file Store / generate the default config file from command line argument and current config at
|
|
191
199
|
/etc/multiSSH3.config.json
|
|
192
200
|
--debug Print debug information
|
|
193
|
-
--
|
|
201
|
+
-ci, --copy_id Copy the ssh id to the hosts
|
|
194
202
|
-V, --version show program's version number and exit
|
|
195
203
|
```
|
|
196
204
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
multiSSH3.py,sha256=ro8ZVf8gHsGf-OrrnSHgmusVEBSOseYbGKeciKqMdO0,119292
|
|
2
|
+
multiSSH3-5.27.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
multiSSH3-5.27.dist-info/METADATA,sha256=kofduHYj68I2ialI_URYPI28HYlMDcwtXjnmxyT-MKY,18160
|
|
4
|
+
multiSSH3-5.27.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
|
5
|
+
multiSSH3-5.27.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
+
multiSSH3-5.27.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
+
multiSSH3-5.27.dist-info/RECORD,,
|
multiSSH3.py
CHANGED
|
@@ -36,7 +36,7 @@ except AttributeError:
|
|
|
36
36
|
# If neither is available, use a dummy decorator
|
|
37
37
|
def cache_decorator(func):
|
|
38
38
|
return func
|
|
39
|
-
version = '5.
|
|
39
|
+
version = '5.27'
|
|
40
40
|
VERSION = version
|
|
41
41
|
|
|
42
42
|
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
@@ -758,7 +758,7 @@ def __filterSumDic(sumDic):
|
|
|
758
758
|
return newSumDic
|
|
759
759
|
|
|
760
760
|
@cache_decorator
|
|
761
|
-
def
|
|
761
|
+
def __compact_hostnames(Hostnames):
|
|
762
762
|
"""
|
|
763
763
|
Compact a list of hostnames.
|
|
764
764
|
Compact numeric numbers into ranges.
|
|
@@ -803,6 +803,43 @@ def compact_hostnames(Hostnames):
|
|
|
803
803
|
rtnSet.add(''.join(hostnameList))
|
|
804
804
|
return frozenset(rtnSet)
|
|
805
805
|
|
|
806
|
+
def compact_hostnames(Hostnames):
|
|
807
|
+
"""
|
|
808
|
+
Compact a list of hostnames.
|
|
809
|
+
Compact numeric numbers into ranges.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
Hostnames (list): A list of hostnames.
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
list: A list of comapcted hostname list.
|
|
816
|
+
|
|
817
|
+
Example:
|
|
818
|
+
>>> compact_hostnames(['server15', 'server16', 'server17'])
|
|
819
|
+
['server[15-17]']
|
|
820
|
+
>>> compact_hostnames(['server-1', 'server-2', 'server-3'])
|
|
821
|
+
['server-[1-3]']
|
|
822
|
+
>>> compact_hostnames(['server-1-2', 'server-1-1', 'server-2-1', 'server-2-2'])
|
|
823
|
+
['server-[1-2]-[1-2]']
|
|
824
|
+
>>> compact_hostnames(['server-1-2', 'server-1-1', 'server-2-2'])
|
|
825
|
+
['server-1-[1-2]', 'server-2-2']
|
|
826
|
+
>>> compact_hostnames(['test1-a', 'test2-a'])
|
|
827
|
+
['test[1-2]-a']
|
|
828
|
+
>>> compact_hostnames(['sub-s1', 'sub-s2'])
|
|
829
|
+
['sub-s[1-2]']
|
|
830
|
+
"""
|
|
831
|
+
global __global_suppress_printout
|
|
832
|
+
if not isinstance(Hostnames, frozenset):
|
|
833
|
+
hostSet = frozenset(Hostnames)
|
|
834
|
+
else:
|
|
835
|
+
hostSet = Hostnames
|
|
836
|
+
compact_hosts = __compact_hostnames(hostSet)
|
|
837
|
+
if set(expand_hostnames(compact_hosts)) != set(expand_hostnames(hostSet)):
|
|
838
|
+
if not __global_suppress_printout:
|
|
839
|
+
eprint(f"Error compacting hostnames: {hostSet} -> {compact_hosts}")
|
|
840
|
+
compact_hosts = hostSet
|
|
841
|
+
return compact_hosts
|
|
842
|
+
|
|
806
843
|
# ------------ Expanding Hostnames ----------------
|
|
807
844
|
@cache_decorator
|
|
808
845
|
def __validate_expand_hostname(hostname):
|
|
@@ -823,10 +860,10 @@ def __validate_expand_hostname(hostname):
|
|
|
823
860
|
return [hostname]
|
|
824
861
|
elif not _no_env and hostname in os.environ:
|
|
825
862
|
# we will expand these hostnames again
|
|
826
|
-
return expand_hostnames(
|
|
863
|
+
return expand_hostnames(os.environ[hostname].split(','))
|
|
827
864
|
elif hostname in readEnvFromFile():
|
|
828
865
|
# we will expand these hostnames again
|
|
829
|
-
return expand_hostnames(
|
|
866
|
+
return expand_hostnames(readEnvFromFile()[hostname].split(','))
|
|
830
867
|
elif getIP(hostname,local=False):
|
|
831
868
|
return [hostname]
|
|
832
869
|
else:
|
|
@@ -940,7 +977,7 @@ def __expand_hostname(text, validate=True):# -> set:
|
|
|
940
977
|
return expandedhosts
|
|
941
978
|
|
|
942
979
|
@cache_decorator
|
|
943
|
-
def
|
|
980
|
+
def __expand_hostnames(hosts) -> dict:
|
|
944
981
|
'''
|
|
945
982
|
Expand the hostnames in the hosts into a dictionary
|
|
946
983
|
|
|
@@ -951,8 +988,6 @@ def expand_hostnames(hosts) -> dict:
|
|
|
951
988
|
dict: A dictionary of expanded hostnames with key: hostname, value: resolved IP address
|
|
952
989
|
'''
|
|
953
990
|
expandedhosts = {}
|
|
954
|
-
if isinstance(hosts, str):
|
|
955
|
-
hosts = [hosts]
|
|
956
991
|
for host in hosts:
|
|
957
992
|
host = host.strip()
|
|
958
993
|
if not host:
|
|
@@ -960,7 +995,12 @@ def expand_hostnames(hosts) -> dict:
|
|
|
960
995
|
# we seperate the username from the hostname
|
|
961
996
|
username = None
|
|
962
997
|
if '@' in host:
|
|
963
|
-
username, host = host.split('@',1)
|
|
998
|
+
username, host = host.split('@',1)
|
|
999
|
+
username = username.strip()
|
|
1000
|
+
host = host.strip()
|
|
1001
|
+
username, host = host.split('@',1)
|
|
1002
|
+
username = username.strip()
|
|
1003
|
+
host = host.strip()
|
|
964
1004
|
# first we check if the hostname is an range of IP addresses
|
|
965
1005
|
# This is done by checking if the hostname follows four fields of
|
|
966
1006
|
# "(((\d{1,3}|x|\*|\?)(-(\d{1,3}|x|\*|\?))?)|(\[(\d{1,3}|x|\*|\?)(-(\d{1,3}|x|\*|\?))?\]))"
|
|
@@ -984,6 +1024,24 @@ def expand_hostnames(hosts) -> dict:
|
|
|
984
1024
|
[expandedhosts.update({host:ip}) for host,ip in zip(hostSetToAdd,iplist)]
|
|
985
1025
|
return expandedhosts
|
|
986
1026
|
|
|
1027
|
+
def expand_hostnames(hosts):
|
|
1028
|
+
'''
|
|
1029
|
+
Expand the hostnames in the hosts into a dictionary
|
|
1030
|
+
|
|
1031
|
+
Args:
|
|
1032
|
+
hosts (list): A list of hostnames
|
|
1033
|
+
|
|
1034
|
+
Returns:
|
|
1035
|
+
dict: A dictionary of expanded hostnames with key: hostname, value: resolved IP address
|
|
1036
|
+
'''
|
|
1037
|
+
if isinstance(hosts, str):
|
|
1038
|
+
hosts = [hosts]
|
|
1039
|
+
# change data type to frozenset if it is not hashable
|
|
1040
|
+
if not isinstance(hosts, frozenset):
|
|
1041
|
+
hosts = frozenset(hosts)
|
|
1042
|
+
return __expand_hostnames(hosts)
|
|
1043
|
+
|
|
1044
|
+
|
|
987
1045
|
# ------------ Run Command Block ----------------
|
|
988
1046
|
def __handle_reading_stream(stream,target, host):
|
|
989
1047
|
'''
|
|
@@ -1761,17 +1819,13 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
1761
1819
|
outputs.setdefault(hostPrintOut, set()).add(host['name'])
|
|
1762
1820
|
rtnStr = ''
|
|
1763
1821
|
for output, hostSet in outputs.items():
|
|
1764
|
-
|
|
1765
|
-
compact_hosts = compact_hostnames(hostSet)
|
|
1766
|
-
if set(expand_hostnames(compact_hosts)) != set(expand_hostnames(hostSet)):
|
|
1767
|
-
eprint(f"Error compacting hostnames: {hostSet} -> {compact_hosts}")
|
|
1768
|
-
compact_hosts = hostSet
|
|
1822
|
+
compact_hosts = sorted(compact_hostnames(hostSet))
|
|
1769
1823
|
if __global_suppress_printout:
|
|
1770
1824
|
rtnStr += f'Abnormal returncode produced by {",".join(compact_hosts)}:\n'
|
|
1771
1825
|
rtnStr += output+'\n'
|
|
1772
1826
|
else:
|
|
1773
1827
|
rtnStr += '*'*80+'\n'
|
|
1774
|
-
rtnStr += f'These hosts: "{",".join(
|
|
1828
|
+
rtnStr += f'These hosts: "{",".join(compact_hosts)}" have a response of:\n'
|
|
1775
1829
|
rtnStr += output+'\n'
|
|
1776
1830
|
if not __global_suppress_printout or outputs:
|
|
1777
1831
|
rtnStr += '*'*80+'\n'
|
|
@@ -2063,13 +2117,13 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2063
2117
|
if '@' not in host:
|
|
2064
2118
|
skipHostStr[i] = userStr + host
|
|
2065
2119
|
skipHostStr = ','.join(skipHostStr)
|
|
2066
|
-
targetHostDic = expand_hostnames(
|
|
2120
|
+
targetHostDic = expand_hostnames(hostStr.split(','))
|
|
2067
2121
|
if __DEBUG_MODE:
|
|
2068
2122
|
eprint(f"Target hosts: {targetHostDic!r}")
|
|
2069
|
-
skipHostsDic = expand_hostnames(
|
|
2070
|
-
if skipHostsDic:
|
|
2071
|
-
eprint(f"Skipping hosts: {skipHostsDic!r}")
|
|
2123
|
+
skipHostsDic = expand_hostnames(skipHostStr.split(','))
|
|
2072
2124
|
skipHostSet = set(skipHostsDic).union(skipHostsDic.values())
|
|
2125
|
+
if skipHostSet:
|
|
2126
|
+
eprint(f"Skipping hosts: \"{' '.join(sorted(compact_hostnames(skipHostSet)))}\"")
|
|
2073
2127
|
if copy_id:
|
|
2074
2128
|
if 'ssh-copy-id' in _binPaths:
|
|
2075
2129
|
# we will copy the id to the hosts
|
|
@@ -2419,18 +2473,15 @@ def main():
|
|
|
2419
2473
|
succeededHosts.add(host.name)
|
|
2420
2474
|
succeededHosts -= __failedHosts
|
|
2421
2475
|
# sort the failed hosts and succeeded hosts
|
|
2422
|
-
__failedHosts = sorted(__failedHosts)
|
|
2423
|
-
succeededHosts = sorted(succeededHosts)
|
|
2424
2476
|
if __mainReturnCode > 0:
|
|
2425
|
-
if not __global_suppress_printout:
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
if not __global_suppress_printout: eprint(f'failed_hosts: {",".join(__failedHosts)}')
|
|
2477
|
+
if not __global_suppress_printout:
|
|
2478
|
+
eprint(f'Complete. Failed hosts (Return Code not 0) count: {__mainReturnCode}')
|
|
2479
|
+
eprint(f'failed_hosts: {",".join(sorted(compact_hostnames(__failedHosts)))}')
|
|
2429
2480
|
else:
|
|
2430
2481
|
if not __global_suppress_printout: eprint('Complete. All hosts returned 0.')
|
|
2431
2482
|
|
|
2432
2483
|
if args.success_hosts and not __global_suppress_printout:
|
|
2433
|
-
eprint(f'succeeded_hosts: {",".join(succeededHosts)}')
|
|
2484
|
+
eprint(f'succeeded_hosts: {",".join(sorted(compact_hostnames(succeededHosts)))}')
|
|
2434
2485
|
|
|
2435
2486
|
if threading.active_count() > 1:
|
|
2436
2487
|
if not __global_suppress_printout: eprint(f'Remaining active thread: {threading.active_count()}')
|
multiSSH3-5.24.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=C8mJAlGUn16EPb-S3qn2h4EsdQ9WSBrgdgFsGb1PDHE,117988
|
|
2
|
-
multiSSH3-5.24.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
multiSSH3-5.24.dist-info/METADATA,sha256=dkIUsooONdhyTGriNyRaW08ai0VUyCKA-SQA6Y47ZQA,17517
|
|
4
|
-
multiSSH3-5.24.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
|
5
|
-
multiSSH3-5.24.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
6
|
-
multiSSH3-5.24.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
7
|
-
multiSSH3-5.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|