multiSSH3 4.97__py3-none-any.whl → 4.98__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: multiSSH3
3
- Version: 4.97
3
+ Version: 4.98
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
@@ -0,0 +1,7 @@
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,,
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.97'
33
+ version = '4.98'
34
34
  VERSION = version
35
35
 
36
36
  CONFIG_FILE = '/etc/multiSSH3.config.json'
@@ -115,6 +115,7 @@ __build_in_default_config = {
115
115
  '_rsyncPath': None,
116
116
  '_bashPath': None,
117
117
  '__ERROR_MESSAGES_TO_IGNORE_REGEX':None,
118
+ '__DEBUG_MODE': False,
118
119
  }
119
120
 
120
121
  AUTHOR = __configs_from_file.get('AUTHOR', __build_in_default_config['AUTHOR'])
@@ -168,6 +169,8 @@ if __ERROR_MESSAGES_TO_IGNORE_REGEX:
168
169
  else:
169
170
  __ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
170
171
 
172
+ __DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config['__DEBUG_MODE'])
173
+
171
174
 
172
175
 
173
176
  __global_suppress_printout = True
@@ -191,8 +194,6 @@ def get_i():
191
194
 
192
195
  class Host:
193
196
  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
196
197
  self.name = name # the name of the host (hostname or IP address)
197
198
  self.command = command # the command to run on the host
198
199
  self.returncode = None # the return code of the command
@@ -675,6 +676,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
675
676
  global __ERROR_MESSAGES_TO_IGNORE_REGEX
676
677
  global __ipmiiInterfaceIPPrefix
677
678
  global _binPaths
679
+ global __DEBUG_MODE
678
680
  try:
679
681
  keyCheckArgs = []
680
682
  rsyncKeyCheckArgs = []
@@ -737,6 +739,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
737
739
  formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}'] + extraargs + [host.command]
738
740
  elif 'ssh' in _binPaths:
739
741
  host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
742
+ if __DEBUG_MODE:
743
+ host.stderr.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
740
744
  host.ipmi = False
741
745
  host.interface_ip_prefix = None
742
746
  host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
@@ -754,6 +758,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
754
758
  useScp = True
755
759
  elif 'rsync' in _binPaths:
756
760
  host.output.append('scp not found on the local machine! Trying to use rsync...')
761
+ if __DEBUG_MODE:
762
+ host.stderr.append('scp not found on the local machine! Trying to use rsync...')
757
763
  useScp = False
758
764
  else:
759
765
  host.output.append('scp not found on the local machine! Please install scp or rsync to use file sync mode.')
@@ -764,6 +770,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
764
770
  useScp = False
765
771
  elif 'scp' in _binPaths:
766
772
  host.output.append('rsync not found on the local machine! Trying to use scp...')
773
+ if __DEBUG_MODE:
774
+ host.stderr.append('rsync not found on the local machine! Trying to use scp...')
767
775
  useScp = True
768
776
  else:
769
777
  host.output.append('rsync not found on the local machine! Please install rsync or scp to use file sync mode.')
@@ -784,7 +792,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
784
792
  formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
785
793
  elif passwds:
786
794
  host.output.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
787
- #host.stderr.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
795
+ if __DEBUG_MODE:
796
+ host.stderr.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
788
797
  host.output.append('Please provide password via live input or use ssh key authentication.')
789
798
  # # try to send the password via __keyPressesIn
790
799
  # __keyPressesIn[-1] = list(passwds) + ['\n']
@@ -802,6 +811,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
802
811
  with sem:
803
812
  try:
804
813
  host.output.append('Running command: '+' '.join(formatedCMD))
814
+ if __DEBUG_MODE:
815
+ host.stderr.append('Running command: '+' '.join(formatedCMD))
805
816
  #host.stdout = []
806
817
  proc = subprocess.Popen(formatedCMD,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)
807
818
  # create a thread to handle stdout
@@ -890,6 +901,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
890
901
  if host.ipmi and host.returncode != 0 and any(['Unable to establish IPMI' in line for line in host.stderr]):
891
902
  host.stderr = []
892
903
  host.output.append('IPMI connection failed! Trying SSH connection...')
904
+ if __DEBUG_MODE:
905
+ host.stderr.append('IPMI connection failed! Trying SSH connection...')
893
906
  host.ipmi = False
894
907
  host.interface_ip_prefix = None
895
908
  host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
@@ -899,6 +912,8 @@ def ssh_command(host, sem, timeout=60,passwds=None):
899
912
  host.stderr = []
900
913
  host.stdout = []
901
914
  host.output.append('Rsync connection failed! Trying SCP connection...')
915
+ if __DEBUG_MODE:
916
+ host.stderr.append('Rsync connection failed! Trying SCP connection...')
902
917
  host.scp = True
903
918
  ssh_command(host,sem,timeout,passwds)
904
919
 
@@ -1393,6 +1408,8 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
1393
1408
  # update the unavailable hosts and global unavailable hosts
1394
1409
  if willUpdateUnreachableHosts:
1395
1410
  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!'))])
1411
+ if __DEBUG_MODE:
1412
+ print(f'Unreachable hosts: {unavailableHosts}')
1396
1413
  __globalUnavailableHosts.update(unavailableHosts)
1397
1414
  # update the os environment variable if not _no_env
1398
1415
  if not _no_env:
@@ -1523,6 +1540,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
1523
1540
  global __global_suppress_printout
1524
1541
  global _no_env
1525
1542
  global _emo
1543
+ global __DEBUG_MODE
1526
1544
  _emo = False
1527
1545
  _no_env = no_env
1528
1546
  if not no_env and '__multiSSH3_UNAVAILABLE_HOSTS' in os.environ:
@@ -1585,6 +1603,8 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
1585
1603
  skipHostStr[i] = userStr + host
1586
1604
  skipHostStr = ','.join(skipHostStr)
1587
1605
  targetHostsList = expand_hostnames(frozenset(hostStr.split(',')))
1606
+ if __DEBUG_MODE:
1607
+ eprint(f"Target hosts: {targetHostsList}")
1588
1608
  skipHostsList = expand_hostnames(frozenset(skipHostStr.split(',')))
1589
1609
  if skipHostsList:
1590
1610
  eprint(f"Skipping hosts: {skipHostsList}")
@@ -1614,6 +1634,8 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
1614
1634
  files = []
1615
1635
  else:
1616
1636
  files = list(pathSet)
1637
+ if __DEBUG_MODE:
1638
+ eprint(f"Files: {files}")
1617
1639
  if oneonone:
1618
1640
  hosts = []
1619
1641
  if len(commands) != len(targetHostsList) - len(skipHostsList):
@@ -1748,13 +1770,14 @@ def main():
1748
1770
  global __ipmiiInterfaceIPPrefix
1749
1771
  global _binPaths
1750
1772
  global _env_file
1773
+ global __DEBUG_MODE
1751
1774
  _emo = False
1752
1775
  # We handle the signal
1753
1776
  signal.signal(signal.SIGINT, signal_handler)
1754
1777
  # We parse the arguments
1755
1778
  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
1779
  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='+',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.')
1780
+ 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
1781
  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
1782
  parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
1760
1783
  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)
@@ -1785,11 +1808,26 @@ def main():
1785
1808
  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
1809
  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
1810
  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
+ parser.add_argument('--debug', action='store_true', help='Print debug information')
1812
+ parser.add_argument('--copy-id', action='store_true', help='Copy the ssh id to the hosts')
1788
1813
  parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
1789
1814
 
1790
1815
  # parser.add_argument('-u', '--user', metavar='user', type=str, nargs=1,
1791
1816
  # help='the user to use to connect to the hosts')
1792
- args = parser.parse_args()
1817
+ #args = parser.parse_args()
1818
+
1819
+ # if python version is 3.7 or higher, use parse_intermixed_args
1820
+ if sys.version_info >= (3,7):
1821
+ args = parser.parse_intermixed_args()
1822
+ else:
1823
+ # try to parse the arguments using parse_known_args
1824
+ args, unknown = parser.parse_known_args()
1825
+ # if there are unknown arguments, we will try to parse them again using parse_args
1826
+ if unknown:
1827
+ eprint(f"Warning: Unknown arguments, treating all as commands: {unknown}")
1828
+ args.commands += unknown
1829
+
1830
+
1793
1831
 
1794
1832
  if args.store_config_file:
1795
1833
  try:
@@ -1816,6 +1854,7 @@ def main():
1816
1854
  sys.exit(0)
1817
1855
 
1818
1856
  _env_file = args.env_file
1857
+ __DEBUG_MODE = args.debug
1819
1858
  # if there are more than 1 commands, and every command only consists of one word,
1820
1859
  # we will ask the user to confirm if they want to run multiple commands or just one command.
1821
1860
  if not args.file and len(args.commands) > 1 and all([len(command.split()) == 1 for command in args.commands]):
@@ -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,,