multiSSH3 5.36__tar.gz → 5.39__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: multiSSH3
3
- Version: 5.36
3
+ Version: 5.39
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
@@ -13,6 +13,15 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: argparse
15
15
  Requires-Dist: ipaddress
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
16
25
 
17
26
  # multiSSH3
18
27
  A script that is able to issue commands to multiple hosts while monitoring their progress.
@@ -43,6 +52,15 @@ mssh --store_config_file
43
52
 
44
53
  You can modify the json file directly after generation and multissh will read from it for loading defaults.
45
54
 
55
+ ```bash
56
+ mssh --ipmi_interface_ip_prefix 192 --store_config_file
57
+ ```
58
+ will store
59
+ ```json
60
+ "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
61
+ ```
62
+ into the json file.
63
+
46
64
  Note:
47
65
 
48
66
  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.
@@ -53,15 +71,6 @@ On some systems, scp / rsync will require you use a priv-pub key to work
53
71
 
54
72
  This option can also be used to store cli options into the config files. For example.
55
73
 
56
- ```bash
57
- mssh --ipmi_interface_ip_prefix 192 --store_config_file
58
- ```
59
- will store
60
- ```json
61
- "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
62
- ```
63
- into the json file.
64
-
65
74
  By defualt reads bash env variables for hostname aliases. Also able to read
66
75
  ```bash
67
76
  DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
@@ -77,7 +86,7 @@ us_east='100.100.0.1-3,us_east_prod_[1-5]'
77
86
  us_central=""
78
87
  us_west="100.101.0.1-2,us_west_prod_[a-c]_[1-3]"
79
88
  us="$us_east,$us_central,$us_west"
80
- asia="100.90.0-1,1-9"
89
+ asia="100.90.0-1.1-9"
81
90
  eu=''
82
91
  rhel8="$asia,$us_east"
83
92
  all="$us,$asia,$eu"
@@ -27,6 +27,15 @@ mssh --store_config_file
27
27
 
28
28
  You can modify the json file directly after generation and multissh will read from it for loading defaults.
29
29
 
30
+ ```bash
31
+ mssh --ipmi_interface_ip_prefix 192 --store_config_file
32
+ ```
33
+ will store
34
+ ```json
35
+ "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
36
+ ```
37
+ into the json file.
38
+
30
39
  Note:
31
40
 
32
41
  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.
@@ -37,15 +46,6 @@ On some systems, scp / rsync will require you use a priv-pub key to work
37
46
 
38
47
  This option can also be used to store cli options into the config files. For example.
39
48
 
40
- ```bash
41
- mssh --ipmi_interface_ip_prefix 192 --store_config_file
42
- ```
43
- will store
44
- ```json
45
- "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
46
- ```
47
- into the json file.
48
-
49
49
  By defualt reads bash env variables for hostname aliases. Also able to read
50
50
  ```bash
51
51
  DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
@@ -61,7 +61,7 @@ us_east='100.100.0.1-3,us_east_prod_[1-5]'
61
61
  us_central=""
62
62
  us_west="100.101.0.1-2,us_west_prod_[a-c]_[1-3]"
63
63
  us="$us_east,$us_central,$us_west"
64
- asia="100.90.0-1,1-9"
64
+ asia="100.90.0-1.1-9"
65
65
  eu=''
66
66
  rhel8="$asia,$us_east"
67
67
  all="$us,$asia,$eu"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: multiSSH3
3
- Version: 5.36
3
+ Version: 5.39
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
@@ -13,6 +13,15 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: argparse
15
15
  Requires-Dist: ipaddress
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
16
25
 
17
26
  # multiSSH3
18
27
  A script that is able to issue commands to multiple hosts while monitoring their progress.
@@ -43,6 +52,15 @@ mssh --store_config_file
43
52
 
44
53
  You can modify the json file directly after generation and multissh will read from it for loading defaults.
45
54
 
55
+ ```bash
56
+ mssh --ipmi_interface_ip_prefix 192 --store_config_file
57
+ ```
58
+ will store
59
+ ```json
60
+ "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
61
+ ```
62
+ into the json file.
63
+
46
64
  Note:
47
65
 
48
66
  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.
@@ -53,15 +71,6 @@ On some systems, scp / rsync will require you use a priv-pub key to work
53
71
 
54
72
  This option can also be used to store cli options into the config files. For example.
55
73
 
56
- ```bash
57
- mssh --ipmi_interface_ip_prefix 192 --store_config_file
58
- ```
59
- will store
60
- ```json
61
- "DEFAULT_IPMI_INTERFACE_IP_PREFIX": "192"
62
- ```
63
- into the json file.
64
-
65
74
  By defualt reads bash env variables for hostname aliases. Also able to read
66
75
  ```bash
67
76
  DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
@@ -77,7 +86,7 @@ us_east='100.100.0.1-3,us_east_prod_[1-5]'
77
86
  us_central=""
78
87
  us_west="100.101.0.1-2,us_west_prod_[a-c]_[1-3]"
79
88
  us="$us_east,$us_central,$us_west"
80
- asia="100.90.0-1,1-9"
89
+ asia="100.90.0-1.1-9"
81
90
  eu=''
82
91
  rhel8="$asia,$us_east"
83
92
  all="$us,$asia,$eu"
@@ -37,7 +37,7 @@ except AttributeError:
37
37
  # If neither is available, use a dummy decorator
38
38
  def cache_decorator(func):
39
39
  return func
40
- version = '5.36'
40
+ version = '5.39'
41
41
  VERSION = version
42
42
 
43
43
  CONFIG_FILE = '/etc/multiSSH3.config.json'
@@ -158,7 +158,7 @@ def _get_i():
158
158
 
159
159
  # ------------ Host Object ----------------
160
160
  class Host:
161
- def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,bash=False,i = _get_i(),uuid=uuid.uuid4(),ip = None):
161
+ def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,shell=False,i = _get_i(),uuid=uuid.uuid4(),ip = None):
162
162
  self.name = name # the name of the host (hostname or IP address)
163
163
  self.command = command # the command to run on the host
164
164
  self.returncode = None # the return code of the command
@@ -170,7 +170,7 @@ class Host:
170
170
  self.lastUpdateTime = time.time() # the last time the output was updated
171
171
  self.files = files # the files to be copied to the host
172
172
  self.ipmi = ipmi # whether to use ipmi to connect to the host
173
- self.bash = bash # whether to use bash to run the command
173
+ self.shell = shell # whether to use shell to run the command
174
174
  self.interface_ip_prefix = interface_ip_prefix # the prefix of the ip address of the interface to be used to connect to the host
175
175
  self.scp = scp # whether to use scp to copy files to the host
176
176
  self.gatherMode = gatherMode # whether the host is in gather mode
@@ -272,7 +272,7 @@ __build_in_default_config = {
272
272
  '_scpPath': None,
273
273
  '_ipmitoolPath': None,
274
274
  '_rsyncPath': None,
275
- '_bashPath': None,
275
+ '_shellPath': None,
276
276
  '__ERROR_MESSAGES_TO_IGNORE_REGEX':None,
277
277
  '__DEBUG_MODE': False,
278
278
  }
@@ -390,7 +390,7 @@ def check_path(program_name):
390
390
  return True
391
391
  return False
392
392
 
393
- [check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash','ssh-copy-id']]
393
+ [check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id']]
394
394
 
395
395
  def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
396
396
  '''
@@ -472,6 +472,7 @@ def replace_magic_strings(string,keys,value,case_sensitive=False):
472
472
  return string
473
473
 
474
474
  def pretty_format_table(data):
475
+ version = 1.0
475
476
  if not data:
476
477
  return ''
477
478
  if type(data) == str:
@@ -488,7 +489,6 @@ def pretty_format_table(data):
488
489
  data = [[key] + list(value) for key, value in data.items()]
489
490
  elif type(data) != list:
490
491
  data = list(data)
491
- # TODO : add support for list of dictionaries
492
492
  # format the list into 2d list of list of strings
493
493
  if isinstance(data[0], dict):
494
494
  tempData = [data[0].keys()]
@@ -833,7 +833,7 @@ def __compact_hostnames(Hostnames):
833
833
  rtnSet.add(''.join(hostnameList))
834
834
  return frozenset(rtnSet)
835
835
 
836
- def compact_hostnames(Hostnames):
836
+ def compact_hostnames(Hostnames,verify = True):
837
837
  """
838
838
  Compact a list of hostnames.
839
839
  Compact numeric numbers into ranges.
@@ -864,10 +864,11 @@ def compact_hostnames(Hostnames):
864
864
  else:
865
865
  hostSet = Hostnames
866
866
  compact_hosts = __compact_hostnames(hostSet)
867
- if set(expand_hostnames(compact_hosts)) != set(expand_hostnames(hostSet)):
868
- if not __global_suppress_printout:
869
- eprint(f"Error compacting hostnames: {hostSet} -> {compact_hosts}")
870
- compact_hosts = hostSet
867
+ if verify:
868
+ if set(expand_hostnames(compact_hosts)) != set(expand_hostnames(hostSet)):
869
+ if not __global_suppress_printout:
870
+ eprint(f"Error compacting hostnames: {hostSet} -> {compact_hosts}")
871
+ compact_hosts = hostSet
871
872
  return compact_hosts
872
873
 
873
874
  # ------------ Expanding Hostnames ----------------
@@ -883,7 +884,6 @@ def __validate_expand_hostname(hostname):
883
884
  list: A list of valid hostnames
884
885
  '''
885
886
  global _no_env
886
- # maybe it is just defined in ./target_files/hosts.sh and exported to the environment
887
887
  # we will try to get the valid host name from the environment
888
888
  hostname = hostname.strip().strip('$')
889
889
  if getIP(hostname,local=True):
@@ -1018,6 +1018,8 @@ def __expand_hostnames(hosts) -> dict:
1018
1018
  dict: A dictionary of expanded hostnames with key: hostname, value: resolved IP address
1019
1019
  '''
1020
1020
  expandedhosts = {}
1021
+ if isinstance(hosts, str):
1022
+ hosts = [hosts]
1021
1023
  for host in hosts:
1022
1024
  host = host.strip()
1023
1025
  if not host:
@@ -1220,12 +1222,12 @@ def run_command(host, sem, timeout=60,passwds=None):
1220
1222
  host.username = 'admin'
1221
1223
  if not host.command:
1222
1224
  host.command = 'power status'
1223
- if 'bash' in _binPaths:
1225
+ if 'sh' in _binPaths:
1224
1226
  if passwds:
1225
- formatedCMD = [_binPaths['bash'],'-c',f'ipmitool -H {host.address} -U {host.username} -P {passwds} {" ".join(extraargs)} {host.command}']
1227
+ formatedCMD = [_binPaths['sh'],'-c',f'ipmitool -H {host.address} -U {host.username} -P {passwds} {" ".join(extraargs)} {host.command}']
1226
1228
  else:
1227
1229
  host.output.append('Warning: Password not provided for ipmi! Using a default password `admin`.')
1228
- formatedCMD = [_binPaths['bash'],'-c',f'ipmitool -H {host.address} -U {host.username} -P admin {" ".join(extraargs)} {host.command}']
1230
+ formatedCMD = [_binPaths['sh'],'-c',f'ipmitool -H {host.address} -U {host.username} -P admin {" ".join(extraargs)} {host.command}']
1229
1231
  else:
1230
1232
  if passwds:
1231
1233
  formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}',f'-P {passwds}'] + extraargs + [host.command]
@@ -1249,17 +1251,17 @@ def run_command(host, sem, timeout=60,passwds=None):
1249
1251
  host.stderr.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
1250
1252
  host.returncode = 1
1251
1253
  return
1252
- elif host.bash:
1253
- if 'bash' in _binPaths:
1254
- host.output.append('Running command in bash mode, ignoring the hosts...')
1254
+ elif host.shell:
1255
+ if 'sh' in _binPaths:
1256
+ host.output.append('Running command in shell mode, ignoring the hosts...')
1255
1257
  if __DEBUG_MODE:
1256
- host.stderr.append('Running command in bash mode, ignoring the hosts...')
1257
- formatedCMD = [_binPaths['bash'],'-c',host.command]
1258
+ host.stderr.append('Running command in shell mode, ignoring the hosts...')
1259
+ formatedCMD = [_binPaths['sh'],'-c',host.command]
1258
1260
  else:
1259
- host.output.append('Bash not found on the local machine! Using ssh localhost instead...')
1261
+ host.output.append('shell not found on the local machine! Using ssh localhost instead...')
1260
1262
  if __DEBUG_MODE:
1261
- host.stderr.append('Bash not found on the local machine! Using ssh localhost instead...')
1262
- host.bash = False
1263
+ host.stderr.append('shell not found on the local machine! Using ssh localhost instead...')
1264
+ host.shell = False
1263
1265
  host.name = 'localhost'
1264
1266
  run_command(host,sem,timeout,passwds)
1265
1267
  else:
@@ -1547,6 +1549,7 @@ def __get_curses_color_pair(fg, bg):
1547
1549
  if __curses_current_color_pair_index >= curses.COLOR_PAIRS:
1548
1550
  print("Warning: Maximum number of color pairs reached, wrapping around.")
1549
1551
  __curses_current_color_pair_index = 1
1552
+ # TODO: avoid initializing the same fg and bg color
1550
1553
  curses.init_pair(__curses_current_color_pair_index, fg, bg)
1551
1554
  __curses_global_color_pairs[(fg, bg)] = __curses_current_color_pair_index
1552
1555
  __curses_current_color_pair_index += 1
@@ -1725,8 +1728,8 @@ def _curses_add_string_to_window(window, line = '', y = 0, x = 0, number_of_char
1725
1728
  # process centering
1726
1729
  if centered:
1727
1730
  fill_length = numChar - len(lead_str) - len(trail_str) - sum([len(segment) for segment in segments if not segment.startswith("\x1b[")])
1728
- window.addnstr(y, x + charsWritten, fill_char * (fill_length // 2), numChar - charsWritten, boxAttr)
1729
- charsWritten += min(fill_length // 2, numChar - charsWritten)
1731
+ window.addnstr(y, x + charsWritten, fill_char * (fill_length // 2 // len(fill_char)), numChar - charsWritten, boxAttr)
1732
+ charsWritten += min(len(fill_char * (fill_length // 2)), numChar - charsWritten)
1730
1733
  # add the segments
1731
1734
  for segment in segments:
1732
1735
  if not segment:
@@ -1741,7 +1744,7 @@ def _curses_add_string_to_window(window, line = '', y = 0, x = 0, number_of_char
1741
1744
  charsWritten += min(len(segment), numChar - charsWritten)
1742
1745
  # if we have finished printing segments but we still have space, we will fill it with fill_char
1743
1746
  if charsWritten + len(trail_str) < numChar:
1744
- fillStr = fill_char * (numChar - charsWritten - len(trail_str))
1747
+ fillStr = fill_char * ((numChar - charsWritten - len(trail_str))//len(fill_char))
1745
1748
  #fillStr = f'{color_pair_list}'
1746
1749
  window.addnstr(y, x + charsWritten, fillStr + trail_str, numChar - charsWritten, boxAttr)
1747
1750
  charsWritten += numChar - charsWritten
@@ -2319,7 +2322,7 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
2319
2322
  if gather_mode: argsList.append('--gather_mode' if not shortend else '-gm')
2320
2323
  if username and username != DEFAULT_USERNAME: argsList.append(f'--username="{username}"' if not shortend else f'-u="{username}"')
2321
2324
  if extraargs and extraargs != DEFAULT_EXTRA_ARGS: argsList.append(f'--extraargs="{extraargs}"' if not shortend else f'-ea="{extraargs}"')
2322
- if skipUnreachable: argsList.append('--skipUnreachable' if not shortend else '-su')
2325
+ if skipUnreachable: argsList.append('--skip_unreachable' if not shortend else '-su')
2323
2326
  if no_env: argsList.append('--no_env')
2324
2327
  if greppable: argsList.append('--greppable' if not shortend else '-g')
2325
2328
  if error_only: argsList.append('--error_only' if not shortend else '-eo')
@@ -2506,7 +2509,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2506
2509
  command = f"{command}{host}"
2507
2510
  if password and 'sshpass' in _binPaths:
2508
2511
  command = f"{_binPaths['sshpass']} -p {password} {command}"
2509
- hosts.append(Host(host, command,identity_file=identity_file,bash=True,ip = targetHostDic[host]))
2512
+ hosts.append(Host(host, command,identity_file=identity_file,shell=True,ip = targetHostDic[host]))
2510
2513
  else:
2511
2514
  eprint(f"> {command}")
2512
2515
  os.system(command)
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name='multiSSH3',
5
- version='5.36',
5
+ version='5.39',
6
6
  description='Run commands on multiple hosts via SSH',
7
7
  long_description=open('README.md').read(),
8
8
  long_description_content_type='text/markdown',
File without changes
File without changes