sshreader 6.0.0__tar.gz → 6.2.0__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,47 +1,39 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sshreader
3
- Version: 6.0.0
3
+ Version: 6.2.0
4
4
  Summary: Multi-threading/processing wrapper for Paramiko
5
- Home-page: http://sshreader.readthedocs.io/
5
+ Author-Email: Jesse Almanrode <jesse@almanrode.com>
6
6
  License: LGPL-3.0-or-later
7
- Author: Jesse Almanrode
8
- Author-email: jesse@almanrode.com
9
- Requires-Python: >3.8,<3.13
10
- Classifier: Development Status :: 5 - Production/Stable
7
+ Classifier: Programming Language :: Python
11
8
  Classifier: Intended Audience :: Developers
12
9
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
10
+ Classifier: Development Status :: 5 - Production/Stable
13
11
  Classifier: Programming Language :: Python
14
- Classifier: Programming Language :: Python :: 3
15
12
  Classifier: Programming Language :: Python :: 3.9
16
13
  Classifier: Programming Language :: Python :: 3.10
17
14
  Classifier: Programming Language :: Python :: 3.11
18
15
  Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.13
20
17
  Classifier: Topic :: Software Development :: Libraries
21
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Dist: click (>=8,<9)
23
- Requires-Dist: colorama (>=0.4,<0.5)
24
- Requires-Dist: paramiko (>=3,<4)
25
- Requires-Dist: progressbar2 (>=4,<5)
26
- Requires-Dist: python-hostlist (>=1,<2)
27
- Project-URL: Repository, https://bitbucket.org/isaiah1112/sshreader/
19
+ Project-URL: homepage, http://sshreader.readthedocs.io/
20
+ Project-URL: repository, https://bitbucket.org/isaiah1112/sshreader/
21
+ Requires-Python: <4.0,>3.9
22
+ Requires-Dist: click<9,>=8
23
+ Requires-Dist: colorama<1.0,>=0.4
24
+ Requires-Dist: paramiko<4,>=3
25
+ Requires-Dist: progressbar2<5,>=4
26
+ Requires-Dist: python-hostlist<2,>=1
28
27
  Description-Content-Type: text/markdown
29
28
 
30
- # [sshreader][]
29
+ ![logo](./logo.png)
31
30
 
32
- ## Overview
31
+ A Python Module for multiprocessing/threading ssh connections in order to make ssh operations
32
+ across multiple servers parallel. It utilizes the [Paramiko](http://www.paramiko.org/) module
33
+ for its ssh client.
33
34
 
34
- [SSHreader][] is a Python Module for multiprocessing/threading ssh connections in order to make ssh operations
35
- across multiple servers parallel. It utilizes the [Paramiko](http://www.paramiko.org/) module for its ssh client.
36
-
37
- In order to maintain the widest range of compatibility, [SSHreader][] is currently tested using the following versions of
38
- Python:
39
-
40
- * Python3.8
41
- * Python3.9
42
- * Python3.10
43
- * Python3.11
44
- * Python3.12
35
+ In order to maintain the widest range of compatibility, [SSHreader][] is currently tested
36
+ using [all supported versions of Python!](https://devguide.python.org/versions/)
45
37
 
46
38
  ## License
47
39
 
@@ -55,13 +47,16 @@ The most straightforward way to get the [SSHreader][] module working for you is:
55
47
  ```commandline
56
48
  pip install sshreader
57
49
  ```
50
+ or, if you'd like to use a local install from the repo:
51
+ ```commandline
52
+ make install
53
+ ```
58
54
 
59
55
  This ensures that all the requirements are met.
60
56
 
61
57
  ## Documentation
62
58
 
63
- The documentation for [SSHreader][] can be found [here](https://sshreader.readthedocs.io)
64
-
59
+ The documentation for [SSHreader][] can be found at [ReadTheDocs.](https://sshreader.readthedocs.io)
65
60
 
66
61
  ## Contributing
67
62
 
@@ -69,37 +64,16 @@ Comments and enhancements are very welcome.
69
64
 
70
65
  Report any issues or feature requests on the [BitBucket bug
71
66
  tracker](https://bitbucket.org/isaiah1112/sshreader/issues?status=new&status=open). Please include a minimal
72
- (not-) working example which reproduces the bug and, if appropriate, the
73
- traceback information. Please do not request features already being worked
67
+ not-working example which reproduces the bug and, if appropriate, the
68
+ traceback information. Please do not request features already being worked
74
69
  towards.
75
70
 
76
71
  Code contributions are encouraged: please feel free to [fork the
77
72
  project](https://bitbucket.org/isaiah1112/sshreader) and submit pull requests to the develop branch.
78
73
 
79
- ### Development Installation
80
-
81
- If you are wanting to work on development of [SSHreader][], first ensure [Poetry](https://python-poetry.org) is installed
82
- and then:
83
-
84
- ```commandline
85
- poetry install --with dev
86
- ```
87
-
88
- To ensure all development requirements are met. This will allow you to run the unit/integration tests!
89
-
90
- ### Building Docs
91
-
92
- If you have installed [Poetry](https://python-poetry.org)] you can build its Sphinx Documentation for [SSHreader][] simply by:
93
-
94
- ```commandline
95
- make docs
96
- ```
97
-
98
- Then simply open `docs/build/html/index.html` in your browser.
99
-
100
74
  ## Extras
101
75
 
102
- Included with sshreader is a script called `pydsh`. This works very similar to[pdsh](https://computing.llnl.gov/linux/pdsh.html)
76
+ Included with sshreader is a script called `pydsh`. This works very similar to [pdsh](https://computing.llnl.gov/linux/pdsh.html)
103
77
  but uses sshreader at its core to perform ssh commands in parallel and return the results.
104
78
  The output of `pydsh` can also be piped through the `dshbak` tool that comes with pdsh.
105
79
 
@@ -110,4 +84,3 @@ to process.
110
84
  [GNU Lesser General Public License v3.0]: http://choosealicense.com/licenses/lgpl-3.0/ "LGPL v3"
111
85
 
112
86
  [sshreader]: https://bitbucket.org/isaiah1112/sshreader "SSHreader Package"
113
-
@@ -1,18 +1,11 @@
1
- # [sshreader][]
1
+ ![logo](./logo.png)
2
2
 
3
- ## Overview
3
+ A Python Module for multiprocessing/threading ssh connections in order to make ssh operations
4
+ across multiple servers parallel. It utilizes the [Paramiko](http://www.paramiko.org/) module
5
+ for its ssh client.
4
6
 
5
- [SSHreader][] is a Python Module for multiprocessing/threading ssh connections in order to make ssh operations
6
- across multiple servers parallel. It utilizes the [Paramiko](http://www.paramiko.org/) module for its ssh client.
7
-
8
- In order to maintain the widest range of compatibility, [SSHreader][] is currently tested using the following versions of
9
- Python:
10
-
11
- * Python3.8
12
- * Python3.9
13
- * Python3.10
14
- * Python3.11
15
- * Python3.12
7
+ In order to maintain the widest range of compatibility, [SSHreader][] is currently tested
8
+ using [all supported versions of Python!](https://devguide.python.org/versions/)
16
9
 
17
10
  ## License
18
11
 
@@ -26,13 +19,16 @@ The most straightforward way to get the [SSHreader][] module working for you is:
26
19
  ```commandline
27
20
  pip install sshreader
28
21
  ```
22
+ or, if you'd like to use a local install from the repo:
23
+ ```commandline
24
+ make install
25
+ ```
29
26
 
30
27
  This ensures that all the requirements are met.
31
28
 
32
29
  ## Documentation
33
30
 
34
- The documentation for [SSHreader][] can be found [here](https://sshreader.readthedocs.io)
35
-
31
+ The documentation for [SSHreader][] can be found at [ReadTheDocs.](https://sshreader.readthedocs.io)
36
32
 
37
33
  ## Contributing
38
34
 
@@ -40,37 +36,16 @@ Comments and enhancements are very welcome.
40
36
 
41
37
  Report any issues or feature requests on the [BitBucket bug
42
38
  tracker](https://bitbucket.org/isaiah1112/sshreader/issues?status=new&status=open). Please include a minimal
43
- (not-) working example which reproduces the bug and, if appropriate, the
44
- traceback information. Please do not request features already being worked
39
+ not-working example which reproduces the bug and, if appropriate, the
40
+ traceback information. Please do not request features already being worked
45
41
  towards.
46
42
 
47
43
  Code contributions are encouraged: please feel free to [fork the
48
44
  project](https://bitbucket.org/isaiah1112/sshreader) and submit pull requests to the develop branch.
49
45
 
50
- ### Development Installation
51
-
52
- If you are wanting to work on development of [SSHreader][], first ensure [Poetry](https://python-poetry.org) is installed
53
- and then:
54
-
55
- ```commandline
56
- poetry install --with dev
57
- ```
58
-
59
- To ensure all development requirements are met. This will allow you to run the unit/integration tests!
60
-
61
- ### Building Docs
62
-
63
- If you have installed [Poetry](https://python-poetry.org)] you can build its Sphinx Documentation for [SSHreader][] simply by:
64
-
65
- ```commandline
66
- make docs
67
- ```
68
-
69
- Then simply open `docs/build/html/index.html` in your browser.
70
-
71
46
  ## Extras
72
47
 
73
- Included with sshreader is a script called `pydsh`. This works very similar to[pdsh](https://computing.llnl.gov/linux/pdsh.html)
48
+ Included with sshreader is a script called `pydsh`. This works very similar to [pdsh](https://computing.llnl.gov/linux/pdsh.html)
74
49
  but uses sshreader at its core to perform ssh commands in parallel and return the results.
75
50
  The output of `pydsh` can also be piped through the `dshbak` tool that comes with pdsh.
76
51
 
@@ -0,0 +1,83 @@
1
+ [project]
2
+ authors = [
3
+ { name = "Jesse Almanrode", email = "jesse@almanrode.com" },
4
+ ]
5
+ requires-python = "<4.0,>3.9"
6
+ dependencies = [
7
+ "click<9,>=8",
8
+ "colorama<1.0,>=0.4",
9
+ "paramiko<4,>=3",
10
+ "progressbar2<5,>=4",
11
+ "python-hostlist<2,>=1",
12
+ ]
13
+ name = "sshreader"
14
+ version = "6.2.0"
15
+ description = "Multi-threading/processing wrapper for Paramiko"
16
+ readme = "README.md"
17
+ classifiers = [
18
+ "Programming Language :: Python",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
21
+ "Development Status :: 5 - Production/Stable",
22
+ "Programming Language :: Python",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Software Development :: Libraries",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
30
+ ]
31
+
32
+ [project.license]
33
+ text = "LGPL-3.0-or-later"
34
+
35
+ [project.urls]
36
+ homepage = "http://sshreader.readthedocs.io/"
37
+ repository = "https://bitbucket.org/isaiah1112/sshreader/"
38
+
39
+ [project.scripts]
40
+ pydsh = "sshreader.scripts.pydsh:cli"
41
+
42
+ [tool.pdm.build]
43
+ includes = [
44
+ "sshreader",
45
+ "sshreader/scripts",
46
+ ]
47
+
48
+ [tool.ruff.lint]
49
+ select = [
50
+ "E",
51
+ "F",
52
+ "B",
53
+ "UP",
54
+ "B",
55
+ "SIM",
56
+ "I",
57
+ ]
58
+ ignore = [
59
+ "E501",
60
+ "F401",
61
+ ]
62
+
63
+ [[tool.uv.index]]
64
+ name = "test-pypi"
65
+ url = "https://test.pypi.org/simple/"
66
+ publish-url = "https://test.pypi.org/legacy/"
67
+ explicit = true
68
+
69
+ [build-system]
70
+ requires = [
71
+ "pdm-backend",
72
+ ]
73
+ build-backend = "pdm.backend"
74
+
75
+ [dependency-groups]
76
+ test = [
77
+ "coverage<8,>=7",
78
+ "discover<1.0,>=0.4",
79
+ "ruff<1.0.0,>=0.9.9",
80
+ ]
81
+ docs = [
82
+ "sphinx<8,>=7",
83
+ ]
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """A Python Package for parallelizing ssh connections via threading and multiprocessing.
3
2
  """
4
- # Copyright (C) 2015-2024 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -17,11 +16,12 @@
17
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
17
 
19
18
  # For backwards compatibility
20
- from .ssh import SSH, envvars
21
- from .customtypes import Command, EnvVars, Timeout, TimeoutTuple
22
- from .utils import ServerJob, Hook, sshread, echo, cpu_limit, shell_command
23
19
  import logging
24
20
 
21
+ from .customtypes import Command, EnvVars, Timeout, TimeoutTuple
22
+ from .ssh import SSH, envvars
23
+ from .utils import Hook, ServerJob, cpu_limit, echo, shell_command, sshread
24
+
25
25
  __author__ = 'Jesse Almanrode (jesse@almanrode.com)'
26
26
  __all__ = ['ssh', 'customtypes', 'utils']
27
27
 
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """ Typing module for sshreader
3
2
  """
4
- # Copyright (C) 2015-2024 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -15,7 +14,7 @@
15
14
  #
16
15
  # You should have received a copy of the GNU Lesser General Public License
17
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
- from typing import NamedTuple, Optional, Union, Tuple
17
+ from typing import NamedTuple, Optional, Union
19
18
 
20
19
 
21
20
  class Command(NamedTuple):
@@ -34,4 +33,4 @@ class EnvVars(NamedTuple):
34
33
 
35
34
 
36
35
  Timeout = Union[int, float]
37
- TimeoutTuple = Tuple[Timeout, Timeout]
36
+ TimeoutTuple = tuple[Timeout, Timeout]
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """ Scripts portion of sshreader package
3
2
  """
4
- # Copyright (C) 2015-2023 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """ A Pythonic implementation of pdsh powered by sshreader
3
2
  """
4
- # Copyright (C) 2015-2023 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -16,17 +15,20 @@
16
15
  # You should have received a copy of the GNU Lesser General Public License
17
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
17
 
18
+ import logging
19
+ import os
20
+ import sys
19
21
  from collections import defaultdict
20
22
  from hashlib import md5
21
- from hostlist import expand_hostlist, collect_hostlist
23
+
22
24
  import click
23
- import logging
24
- import os
25
+ from hostlist import collect_hostlist, expand_hostlist
26
+
25
27
  import sshreader
26
- import sys
28
+
27
29
  # GLOBALS
28
30
  __author__ = 'Jesse Almanrode'
29
- __version__ = '3.1.0'
31
+ __version__ = '3.2.0'
30
32
  __examples__ = """\b
31
33
  Examples:
32
34
  pydsh -w host1,host2,host3 "uname -r"
@@ -63,10 +65,7 @@ def output(thisjob):
63
65
  :param thisjob: <ServerJob> object
64
66
  :return: None
65
67
  """
66
- if thisjob.status == 255:
67
- result = thisjob.results[0]
68
- else:
69
- result = thisjob.results[0].stdout
68
+ result = thisjob.results[0] if thisjob.status == 255 else thisjob.results[0].stdout
70
69
  if len(result) != 0:
71
70
  for line in result.split('\n'):
72
71
  sshreader.echo(str(thisjob.name) + ': ' + str(line))
@@ -103,13 +102,10 @@ def coalesce(jobresults):
103
102
  job_hashes = defaultdict(list)
104
103
  output_hashes = dict()
105
104
  for job in jobresults:
106
- if job.status == 255:
107
- result = job.results[0]
108
- else:
109
- result = job.results[0].stdout
105
+ result = job.results[0] if job.status == 255 else job.results[0].stdout
110
106
  md5sum = md5(result.encode()).hexdigest()
111
107
  job_hashes[md5sum].append(job.name)
112
- if md5sum not in output_hashes.keys():
108
+ if md5sum not in output_hashes:
113
109
  output_hashes[md5sum] = result
114
110
 
115
111
  for md5sum, stdout in output_hashes.items():
@@ -130,7 +126,7 @@ def validate_hostlist(ctx, param, value):
130
126
  try:
131
127
  return expand_hostlist(value)
132
128
  except Exception:
133
- raise click.BadOptionUsage(param, 'Invalid hostlist expression')
129
+ raise click.BadOptionUsage(param, 'Invalid hostlist expression') from None
134
130
 
135
131
 
136
132
  @click.command(epilog=__examples__)
@@ -255,7 +251,7 @@ def cli(**kwargs):
255
251
  job.pre_hook = prehook
256
252
  jobs.append(job)
257
253
 
258
- log.info('Sending %s ServerJobs to sshreader module' % (len(jobs),))
254
+ log.info(f'Sending {len(jobs)} ServerJobs to sshreader module')
259
255
  if kwargs['dshbak'] is False and kwargs['coalesce'] is False:
260
256
  if kwargs['redline']:
261
257
  sshreader.sshread(jobs, pcount=0, tcount=0, print_lock=True)
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """A wrapper for Paramiko that attempts to make ssh sessions easier to work with.
3
2
  """
4
- # Copyright (C) 2015-2024 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -17,12 +16,14 @@
17
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
17
  import logging
19
18
  import os
20
- import paramiko
21
19
  import socket
22
20
  from getpass import getuser
23
21
  from typing import Any, Optional
24
22
 
23
+ import paramiko
24
+
25
25
  from .customtypes import Command, EnvVars, Timeout
26
+
26
27
  log = logging.getLogger('sshreader')
27
28
 
28
29
 
@@ -56,7 +57,7 @@ def envvars() -> EnvVars:
56
57
  return EnvVars(**env)
57
58
 
58
59
 
59
- class SSH(object):
60
+ class SSH:
60
61
  """SSH Session class which can be used to send commands to a remote server. It also supports basic SFTP commands
61
62
  and can be used to push/pull files from a remote system.
62
63
 
@@ -80,16 +81,14 @@ class SSH(object):
80
81
  """
81
82
  def __init__(self, fqdn: str, username: str, password: Optional[str] = None, keyfile: Optional[str] = None,
82
83
  keypass: Optional[str] = None, port: int = 22, connect: bool = True, rsa_sha2: bool = True) -> None:
83
- if not keyfile:
84
- if len(paramiko.Agent().get_keys()) == 0:
85
- if not all((username, password)):
86
- paramiko.SSHException('username and password or keyfile not provided')
84
+ if not keyfile and len(paramiko.Agent().get_keys()) == 0 and not all((username, password)):
85
+ paramiko.SSHException('username and password or keyfile not provided')
87
86
  self.host = fqdn
88
87
  self.username = username
89
88
  self.password = password
90
89
  if keyfile:
91
90
  if not isinstance(keyfile, str):
92
- raise TypeError('expected %s for keyfile, got %s' % (str(str), str(type(keyfile))))
91
+ raise TypeError(f'expected {str(str)} for keyfile, got {str(type(keyfile))}')
93
92
  self.keyfile = os.path.abspath(os.path.expanduser(keyfile))
94
93
  else:
95
94
  self.keyfile = keyfile
@@ -122,7 +121,7 @@ class SSH(object):
122
121
  :return: Result of :meth:`paramiko.SFTPClient.put()`
123
122
  """
124
123
  if not self.__alive():
125
- raise paramiko.SSHException("connection to %s not established" % (self.host,))
124
+ raise paramiko.SSHException(f"connection to {self.host} not established")
126
125
  sftp = paramiko.SFTPClient.from_transport(self._connection.get_transport())
127
126
  try:
128
127
  result = sftp.put(os.path.expanduser(srcfile), os.path.expanduser(dstfile))
@@ -140,7 +139,7 @@ class SSH(object):
140
139
  :return: None
141
140
  """
142
141
  if not self.__alive():
143
- raise paramiko.SSHException("connection to %s not established" % (self.host,))
142
+ raise paramiko.SSHException(f"connection to {self.host} not established")
144
143
  sftp = paramiko.SFTPClient.from_transport(self._connection.get_transport())
145
144
  try:
146
145
  sftp.get(os.path.expanduser(srcfile), os.path.expanduser(dstfile))
@@ -164,7 +163,7 @@ class SSH(object):
164
163
  :raises: :class:`paramiko.SSHException`
165
164
  """
166
165
  if self.__alive() is False:
167
- raise paramiko.SSHException("connection to %s not established" % (self.host, ))
166
+ raise paramiko.SSHException(f"connection to {self.host} not established")
168
167
  if combine:
169
168
  try:
170
169
  stdin, stdout, stderr = self._connection.exec_command(command, timeout=timeout, get_pty=True)
@@ -226,12 +225,10 @@ class SSH(object):
226
225
  :raises: :class:`paramiko.SSHException`
227
226
  """
228
227
  if self.__alive():
229
- raise paramiko.SSHException("connection to %s already established" % (self.host, ))
228
+ raise paramiko.SSHException(f"connection to {self.host} already established")
230
229
  paramiko.util.logging.getLogger().setLevel(logging.CRITICAL) # Keeping paramiko from logging errors to stdout
231
- if not self.keyfile:
232
- if len(paramiko.Agent().get_keys()) == 0:
233
- if not all((self.username, self.password)):
234
- paramiko.SSHException('username and password or keyfile not provided')
230
+ if not self.keyfile and len(paramiko.Agent().get_keys()) == 0 and not all((self.username, self.password)):
231
+ paramiko.SSHException('username and password or keyfile not provided')
235
232
  if self.keyfile:
236
233
  if self.rsa_sha2:
237
234
  self._connection.connect(self.host, port=self.port, username=self.username, password=self.keypass,
@@ -1,7 +1,6 @@
1
- # coding=utf-8
2
1
  """ All the classes and functions that make sshreader tick
3
2
  """
4
- # Copyright (C) 2015-2024 Jesse Almanrode
3
+ # Copyright (C) 2015-2025 Jesse Almanrode
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Lesser General Public License as published by
@@ -18,17 +17,17 @@
18
17
  import logging
19
18
  import multiprocessing
20
19
  import os
21
- import paramiko
22
20
  import subprocess
23
21
  import sys
24
22
  import threading
25
23
  import time
26
- from progressbar import ProgressBar
27
24
  from typing import Any, Callable, Optional, Union
28
25
 
26
+ import paramiko
27
+ from progressbar import ProgressBar
29
28
 
30
- from .ssh import SSH
31
29
  from .customtypes import Command, Timeout, TimeoutTuple
30
+ from .ssh import SSH
32
31
 
33
32
  # Globals
34
33
  mpctx = multiprocessing.get_context('spawn') # Forcing the forking type to spawn in older versions of Python3
@@ -68,7 +67,7 @@ def shell_command(command: str, combine: bool = False, decode_bytes: bool = True
68
67
  return result
69
68
 
70
69
 
71
- class Hook(object):
70
+ class Hook:
72
71
  """ Custom class for creating "Hooks" that can execute code before of after a ServerJob object executes and
73
72
  can evn act on the data of a ServerJob when it is passed as the first argument to the Hook object.
74
73
 
@@ -117,7 +116,7 @@ class Hook(object):
117
116
  return str(self.__dict__)
118
117
 
119
118
 
120
- class ServerJob(object):
119
+ class ServerJob:
121
120
  """ Custom class for holding all the info needed to run ssh commands or shell commands in sub-processes or threads
122
121
 
123
122
  :param fqdn: Fully qualified domain name or IP address
@@ -194,9 +193,8 @@ class ServerJob(object):
194
193
  self.post_hook = post_hook
195
194
  if run_local:
196
195
  self._conn = 'localhost'
197
- elif not keyfile and len(paramiko.Agent().get_keys()) == 0:
198
- if not all([username, password]):
199
- raise paramiko.SSHException('username and password or ssh key not provided')
196
+ elif not keyfile and len(paramiko.Agent().get_keys()) == 0 and not all([username, password]):
197
+ raise paramiko.SSHException('username and password or ssh key not provided')
200
198
 
201
199
  def run(self) -> int:
202
200
  """Run a ServerJob. SSH to server, run cmds, return result
@@ -204,54 +202,54 @@ class ServerJob(object):
204
202
  :return: Sum of return codes for each command executed
205
203
  :rtype: int
206
204
  """
207
- log.info('%s: starting ServerJob' % (self.name,))
205
+ log.info(f'{self.name}: starting ServerJob')
208
206
  if self.run_local:
209
207
  if self.pre_hook:
210
- log.debug('%s: running prehook' % (self.name,))
208
+ log.debug(f'{self.name}: running prehook')
211
209
  self.pre_hook.run(self)
212
210
  for cmd in self.cmds:
213
211
  result = shell_command(cmd, combine=self.combine_output)
214
- log.debug('%s: %s' % (self.name, str(result)))
212
+ log.debug(f'{self.name}: {str(result)}')
215
213
  self.results.append(result)
216
214
  self.status += result.return_code
217
215
  if self.post_hook:
218
- log.debug('%s; running posthook' % (self.name,))
216
+ log.debug(f'{self.name}; running posthook')
219
217
  self.post_hook.run(self)
220
218
  else:
221
219
  if self.pre_hook and self.pre_hook.ssh_established is False:
222
- log.debug('%s: running prehook' % (self.name,))
220
+ log.debug(f'{self.name}: running prehook')
223
221
  self.pre_hook.run(self)
224
222
  try:
225
223
  self._conn = SSH(self.name, username=self.username, password=self.password, keyfile=self.key,
226
224
  port=self.ssh_port, connect=False, rsa_sha2=self.rsa_sha2)
227
225
  self._conn.connect(timeout=self.ssh_timeout)
228
- log.debug('%s: ssh connection established' % (self.name,))
226
+ log.debug(f'{self.name}: ssh connection established')
229
227
  except Exception as errorMsg:
230
228
  log.debug(str(errorMsg))
231
229
  self.status = 255
232
230
  self.results.append(str(errorMsg))
233
231
  else:
234
232
  if self.pre_hook and self.pre_hook.ssh_established:
235
- log.debug('%s: running prehook' % (self.name,))
233
+ log.debug(f'{self.name}: running prehook')
236
234
  self.pre_hook.run(self)
237
235
  for cmd in self.cmds:
238
236
  try:
239
237
  result = self._conn.ssh_command(cmd, timeout=self.cmd_timeout, combine=self.combine_output)
240
238
  except Exception as errorMsg:
241
239
  result = Command(cmd, '', str(errorMsg), 54)
242
- log.debug('%s: %s' % (self.name, str(result)))
240
+ log.debug(f'{self.name}: {str(result)}')
243
241
  self.results.append(result)
244
242
  self.status += result.return_code
245
243
  if self.post_hook and self.post_hook.ssh_established:
246
- log.debug('%s; running posthook' % (self.name,))
244
+ log.debug(f'{self.name}; running posthook')
247
245
  self.post_hook.run(self)
248
246
  self._conn.close()
249
247
  finally:
250
248
  self._conn = None # So the ssh connection can be pickled!
251
249
  if self.post_hook and self.post_hook.ssh_established is False:
252
- log.debug('%s; running posthook' % (self.name,))
250
+ log.debug(f'{self.name}; running posthook')
253
251
  self.post_hook.run(self)
254
- log.info('%s: exiting ServerJob' % (self.name,))
252
+ log.info(f'{self.name}: exiting ServerJob')
255
253
  return self.status
256
254
 
257
255
  def __str__(self) -> str:
@@ -361,7 +359,7 @@ def sshread(serverjobs: list, pcount: Optional[int] = None, tcount: Optional[int
361
359
  else:
362
360
  tcount = int(min(tcount, totaljobs))
363
361
 
364
- log.info(u'spawning %d threads' % (tcount, ))
362
+ log.info(f'spawning {tcount} threads')
365
363
  # Start a thread pool
366
364
  for thread in range(tcount):
367
365
  thread = threading.Thread(target=_sub_thread_, args=(task_queue, result_queue, item_counter, progress_bar),
@@ -389,7 +387,7 @@ def sshread(serverjobs: list, pcount: Optional[int] = None, tcount: Optional[int
389
387
  # If we don't have enough jobs to spawn more than 1 thread per process, then we won't spawn threads
390
388
  tcount = 0
391
389
 
392
- log.info(u'spawning %d processes' % (pcount, ))
390
+ log.info(f'spawning {pcount} processes')
393
391
  for pid in range(pcount):
394
392
  pid = mpctx.Process(target=_sub_process_,
395
393
  args=(task_queue, result_queue, item_counter, tcount, progress_bar),
@@ -398,7 +396,7 @@ def sshread(serverjobs: list, pcount: Optional[int] = None, tcount: Optional[int
398
396
  pids.append(pid)
399
397
 
400
398
  # Non-blocking way to wait for threads/processes
401
- log.debug('main waiting for %d ServerJobs to finish' % (totaljobs,))
399
+ log.debug(f'main waiting for {totaljobs} ServerJobs to finish')
402
400
  while result_queue.full() is False:
403
401
  if progress_bar:
404
402
  bar.update(item_counter.value)
@@ -407,12 +405,12 @@ def sshread(serverjobs: list, pcount: Optional[int] = None, tcount: Optional[int
407
405
  bar.finish()
408
406
 
409
407
  if len(threads) > 0:
410
- log.info('joining %d threads' % (len(threads),))
408
+ log.info(f'joining {len(threads)} threads')
411
409
  for t in threads:
412
410
  if t.is_alive():
413
411
  t.join(timeout=1)
414
412
  elif len(pids) > 0:
415
- log.info('joining %d processes' % (len(pids),))
413
+ log.info(f'joining {len(pids)} processes')
416
414
  for p in pids:
417
415
  if p.is_alive():
418
416
  p.join(timeout=1)
@@ -433,7 +431,7 @@ def _sub_process_(task_queue, result_queue, item_counter, thread_count, progress
433
431
  DO NOT USE THIS METHOD!
434
432
  """
435
433
  pid = os.getpid()
436
- log.debug(u'starting process: %d' % (pid,))
434
+ log.debug(f'starting process: {pid}')
437
435
  if thread_count == 0:
438
436
  while task_queue.empty() is False:
439
437
  job = task_queue.get()
@@ -444,16 +442,16 @@ def _sub_process_(task_queue, result_queue, item_counter, thread_count, progress
444
442
  item_counter.value += 1
445
443
  else:
446
444
  threads = list()
447
- log.debug(u'process: %d spawning: %d threads' % (pid, thread_count))
445
+ log.debug(f'process: {pid} spawning: {thread_count} threads')
448
446
  for thread in range(thread_count):
449
447
  thread = threading.Thread(target=_sub_thread_, args=(task_queue, result_queue, item_counter, progress_bar),
450
448
  daemon=True)
451
449
  thread.start()
452
450
  threads.append(thread)
453
- log.debug(u'process: %d waiting for: %d threads' % (pid, len(threads)))
451
+ log.debug(f'process: {pid} waiting for: {len(threads)} threads')
454
452
  for thread in threads:
455
453
  thread.join()
456
- log.debug(u'exiting process: %d' % (pid,))
454
+ log.debug(f'exiting process: {pid}')
457
455
  return None
458
456
 
459
457
 
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAvJzuoZadl3gmPy+4f7mMtS/jpbHH7uxIih6aLRGNugF0Q/Xq
3
+ 8bS85mMBlHnz6lu6bDNiLa/Tq4gg8/T9LZ5WTfHWV2Eb4IKiop9k93/GkgSUN7tW
4
+ Ig2GzvSgkqEU5SfR0C4V4N+DhJw5mi40AAEJtc1MN7wHvQ9YctV0L4800OZsn0xl
5
+ LlfIruiP2QhaQ1LY1j0nuevHONxVN65zyOMF9pwp0WdJTiCyMx8NKcWxyi1GNOR+
6
+ 1GdANUP7DXRCOUN/JRJIQ2T04xdg+xIJLsEfNRdp8GKR7Gc7b/uwf2iBXgPhQ1pS
7
+ s3yJOV+1WGG6xJ8RCm5Fw58cP/JTolbeRRFvhQIDAQABAoIBAEP+pyBltcZHMDKT
8
+ N4YEFC+1ZfHz10nQsI3qXlZk/5zQrWNaoiokp/y+MHGgTUhtbRVYqMojmsE5v/dy
9
+ mFr7u805o7W4cl5Leen6YGHBFIrLT8uPo4t/KNwu4Dw8CdZato7ALm705OrHjxSA
10
+ F4rw1NlPe2yUQo3rnE05mjsS+01bRWJ8/K+EazDXAiVyOhcWnX/Q7yafhtwqelen
11
+ sQiVChVk419SM/WZcqC9eeYVxM6JZRUrwV1ntKF4/8brwKazwUibsOhlrpqMB7yC
12
+ HGR27XTwCpKHyBfFtc9XZOn15Nig5weHzcu9pNOlpNiG7Pn4jifSdU8q+9hEdZj+
13
+ lRnr7zkCgYEA7U3Wd0i8ta3Z6os9DAMBuVJrPcyr4RQCkt48fH/CVrEh+IhC/gql
14
+ OEMMmaIoarUI6yJikV7hOBJTn33EhyTMjg3I4jL8mxKLO50557uOuiOmfllX0BHD
15
+ 91HzBPKPju3nxVGHhPeUIlBpGjdalLY5sRGF3jIdCO2yggyJ2P7XGIcCgYEAy3kM
16
+ tCNbM7a5L6WZ5pWbQNyqoqfO6WtrMCIpeAfEW9rYY2MLE6Lc7bPTUQ5NsSMpNtUK
17
+ FLbku90CrYWi3OG//rfWPnhbchr7CJpwKjh0O3YQrzrfCj17RBAFdcUIHbsjPh17
18
+ SBf489gvUFGYXyZTbg3sDgGLUWq7+To3k3lnVpMCgYEAhdjif9N6BSty/3Tb0Pud
19
+ LE9Nh8T6qXBk5zwMd9iD3+v3EjyhNLMCb9J5t+oby9wilKWHXSFb/QlxO0Nv5x4d
20
+ dn5V8Sf7DOiPuec7svY6Uckr7pYsDoMGumfxn7fEu0R69vO+cYRHfHdIYO4Z3ElQ
21
+ DwyoKiSv9cpEZk/2LHvJDN8CgYB1JlPnH3gyNSE4SqtqdHa4CJUCI4XhJOTJCNJB
22
+ do9ap4zw5CSLgLic721afNbEh08Cb/Rm6sqVU3FoQMn9TMkDOgtTWm6kKt6gHHM6
23
+ C9j4J0kB6u5h8fYXwPWz0eaTX5yx9b12S78dfjn3j5MgLWvZsJBZnA//p2lk3F0F
24
+ Pt/9mwKBgQCCRFX8Cgwkz6S+7kIWExZ+Dt3RtpKD+caZIoJaRZ4yHGAB+dHjk/G+
25
+ PDuGGnHRjFU/R20YLm19b+nlwJiE/LcqA+esOyDaOyLCC1H87b0vJVzrqD2XPogL
26
+ zNRVIA1Uz/d5q4eCLP5jMCIPEDXQq34uXYVtzn7A0RG7EU9KCxVhyA==
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1 @@
1
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8nO6hlp2XeCY/L7h/uYy1L+Olscfu7EiKHpotEY26AXRD9erxtLzmYwGUefPqW7psM2Itr9OriCDz9P0tnlZN8dZXYRvggqKin2T3f8aSBJQ3u1YiDYbO9KCSoRTlJ9HQLhXg34OEnDmaLjQAAQm1zUw3vAe9D1hy1XQvjzTQ5myfTGUuV8iu6I/ZCFpDUtjWPSe568c43FU3rnPI4wX2nCnRZ0lOILIzHw0pxbHKLUY05H7UZ0A1Q/sNdEI5Q38lEkhDZPTjF2D7EgkuwR81F2nwYpHsZztv+7B/aIFeA+FDWlKzfIk5X7VYYbrEnxEKbkXDnxw/8lOiVt5FEW+F
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """ Integration and Unit tests for pydsh script
4
+ """
5
+ import os
6
+ import sys
7
+ import unittest
8
+ import warnings
9
+ from click.testing import CliRunner
10
+
11
+ __author__ = 'Jesse Almanrode (jesse@almanrode.com)'
12
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13
+ sys.path.append(project_root)
14
+ import sshreader.scripts.pydsh as pydsh
15
+
16
+ global ssh_data
17
+ # Defaults for testing with Docker!
18
+ ssh_data = {"host_fqdn": "127.0.0.1", "host_port": os.getenv('SSH_PORT', 22),
19
+ "ssh_user": "sshreader", "ssh_password": "sunshine",
20
+ "ssh_key_path": project_root + "/tests/keys/id_rsa"}
21
+
22
+
23
+ class TestPydsh(unittest.TestCase):
24
+ """ Test cases for the pydsh cli
25
+ """
26
+
27
+ def setUp(self) -> None:
28
+ self.cli = CliRunner()
29
+
30
+ def test_help(self):
31
+ self.assertEqual(self.cli.invoke(pydsh.cli, ['--help']).exit_code, 0)
32
+ pass
33
+
34
+ def test_password_auth(self):
35
+ global ssh_data
36
+ cli_result = self.cli.invoke(pydsh.cli, ['-w', ssh_data['host_fqdn'], '--port', ssh_data['host_port'],
37
+ '-u', ssh_data['ssh_user'], '-P', ssh_data['ssh_password'],
38
+ 'uname'])
39
+ self.assertIn('Linux', cli_result.stdout)
40
+ self.assertEqual(cli_result.exit_code, 0)
41
+ pass
42
+
43
+ def test_keyfile_auth(self):
44
+ global ssh_data
45
+ cli_result = self.cli.invoke(pydsh.cli, ['-w', ssh_data['host_fqdn'], '--port', ssh_data['host_port'],
46
+ '-u', ssh_data['ssh_user'], '-k', ssh_data['ssh_key_path'],
47
+ 'uname'])
48
+ self.assertIn('Linux', cli_result.stdout)
49
+ self.assertEqual(cli_result.exit_code, 0)
50
+ pass
51
+
52
+ def test_hostlist_port(self):
53
+ global ssh_data
54
+ host_port = ssh_data['host_fqdn'] + ':' + str(ssh_data['host_port'])
55
+ cli_result = self.cli.invoke(pydsh.cli, ['-w', host_port,
56
+ '-u', ssh_data['ssh_user'], '-k', ssh_data['ssh_key_path'],
57
+ 'uname'])
58
+ self.assertIn('Linux', cli_result.stdout)
59
+ self.assertEqual(cli_result.exit_code, 0)
60
+ pass
61
+
62
+ def test_dshbak_output(self):
63
+ global ssh_data
64
+ cli_result = self.cli.invoke(pydsh.cli, ['-w', ssh_data['host_fqdn'], '--port', ssh_data['host_port'],
65
+ '-u', ssh_data['ssh_user'], '-k', ssh_data['ssh_key_path'],
66
+ '-D', 'uname'])
67
+ self.assertIn('Linux', cli_result.stdout)
68
+ self.assertEqual(cli_result.exit_code, 0)
69
+ pass
70
+
71
+ def test_coalesce_output(self):
72
+ global ssh_data
73
+ cli_result = self.cli.invoke(pydsh.cli, ['-w', ssh_data['host_fqdn'], '--port', ssh_data['host_port'],
74
+ '-u', ssh_data['ssh_user'], '-k', ssh_data['ssh_key_path'],
75
+ '-C', 'uname'])
76
+ self.assertIn('Linux', cli_result.stdout)
77
+ self.assertEqual(cli_result.exit_code, 0)
78
+ pass
79
+
80
+
81
+ if __name__ == '__main__':
82
+ with warnings.catch_warnings(record=True):
83
+ unittest.main()
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """ Integration and Unit tests for sshreader Python Package
4
+ """
5
+ import os
6
+ import sys
7
+ import unittest
8
+ import warnings
9
+
10
+ __author__ = 'Jesse Almanrode (jesse@almanrode.com)'
11
+ project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+ sys.path.append(project_root)
13
+ import sshreader
14
+
15
+ global ssh_data
16
+ # Defaults for testing with Docker!
17
+ ssh_data = {"host_fqdn": "127.0.0.1", "host_port": os.getenv('SSH_PORT', 22),
18
+ "ssh_user": "sshreader", "ssh_password": "sunshine",
19
+ "ssh_key_path": project_root + "/tests/keys/id_rsa"}
20
+
21
+
22
+ class TestMisc(unittest.TestCase):
23
+ """ Test Cases for miscellaneous functions of SSH module
24
+ """
25
+
26
+ def test_env_variables(self):
27
+ """ Test envvars method"""
28
+ ssh_env = sshreader.envvars()
29
+ self.assertIsNotNone(ssh_env.username)
30
+ pass
31
+
32
+ def test_shell_command(self):
33
+ """ Test shell_command method
34
+ """
35
+ result = sshreader.shell_command('echo "foo"')
36
+ self.assertEqual(result.return_code, 0)
37
+ self.assertIn('foo', result.stdout)
38
+ self.assertEqual(len(result.stderr), 0)
39
+ pass
40
+
41
+ def test_shell_command_combined(self):
42
+ """ Test combining stdout and stderr of shell_command method
43
+ """
44
+ result = sshreader.shell_command('echo "foo"; echo "bar" 1>&2', combine=True)
45
+ self.assertEqual(result.return_code, 0)
46
+ self.assertIn('foo', result.stdout)
47
+ self.assertIn('bar', result.stdout)
48
+ self.assertEqual(result.stderr, None)
49
+ pass
50
+
51
+ def test_shell_command_stderr(self):
52
+ """ Test stderr of shell_command method
53
+ """
54
+ result = sshreader.shell_command('echo "bar" 1>&2')
55
+ self.assertEqual(result.return_code, 0)
56
+ self.assertEqual(len(result.stdout), 0)
57
+ self.assertIn('bar', result.stderr)
58
+ pass
59
+
60
+ def test_decode_bytes(self):
61
+ """ Test to ensure that result is a unicode string type
62
+ """
63
+ result = sshreader.shell_command('uname -a')
64
+ self.assertEqual(result.return_code, 0)
65
+ self.assertIsInstance(result.stdout, str)
66
+ pass
67
+
68
+ def test_bytes(self):
69
+ """ Test to ensure that result is a bytes string type
70
+ """
71
+ result = sshreader.shell_command('uname -a', decode_bytes=False)
72
+ self.assertEqual(result.return_code, 0)
73
+ self.assertIsInstance(result.stdout, bytes)
74
+ result = sshreader.shell_command('uname -a', combine=True, decode_bytes=False)
75
+ self.assertEqual(result.return_code, 0)
76
+ self.assertIsInstance(result.stdout, bytes)
77
+ pass
78
+
79
+
80
+ class TestSSH(unittest.TestCase):
81
+ """ Test cases for the SSH class
82
+ """
83
+
84
+ def test_password(self):
85
+ """ Test an SSH connection using a password
86
+ """
87
+ global ssh_data
88
+ with sshreader.SSH(ssh_data['host_fqdn'], port=ssh_data['host_port'], username=ssh_data['ssh_user'],
89
+ password=ssh_data['ssh_password']) as conn:
90
+ self.assertTrue(conn.alive())
91
+ self.assertEqual(conn.ssh_command('uname').stdout, 'Linux')
92
+ pass
93
+
94
+ def test_keyfile(self):
95
+ """ Test an SSH connection using an ssh key
96
+ """
97
+ global ssh_data
98
+ with sshreader.SSH(ssh_data['host_fqdn'], port=ssh_data['host_port'], username=ssh_data['ssh_user'],
99
+ keyfile=ssh_data['ssh_key_path']) as conn:
100
+ self.assertTrue(conn.alive())
101
+ self.assertEqual(conn.ssh_command('uname').stdout, 'Linux')
102
+ pass
103
+
104
+ def test_reconnect(self):
105
+ """ Test re-opening an SSH connection
106
+ """
107
+ global ssh_data
108
+ conn = sshreader.SSH(ssh_data['host_fqdn'], port=ssh_data['host_port'], username=ssh_data['ssh_user'],
109
+ password=ssh_data['ssh_password'], connect=False)
110
+ self.assertFalse(conn.alive())
111
+ conn.reconnect()
112
+ self.assertTrue(conn.alive())
113
+ conn.close()
114
+ pass
115
+
116
+ def test_command(self):
117
+ """ Test commands over ssh
118
+ """
119
+ with sshreader.SSH(ssh_data['host_fqdn'], port=ssh_data['host_port'], username=ssh_data['ssh_user'],
120
+ password=ssh_data['ssh_password']) as conn:
121
+ cmd = conn.ssh_command('uname')
122
+ self.assertEqual(cmd.stdout, 'Linux')
123
+ self.assertEqual(cmd.stderr, '')
124
+ cmd = conn.ssh_command('uname 1>&2')
125
+ self.assertEqual(cmd.stderr, 'Linux')
126
+ self.assertEqual(cmd.stdout, '')
127
+ cmd = conn.ssh_command('echo foo; echo bar 1>&2;', combine=True)
128
+ self.assertIn('foo', cmd.stdout)
129
+ self.assertIn('bar', cmd.stdout)
130
+ self.assertIsNone(cmd.stderr)
131
+ cmd = conn.ssh_command('sleep 5', timeout=2)
132
+ self.assertEqual(cmd.return_code, 124)
133
+ self.assertIn('command timed out', cmd.stderr)
134
+ pass
135
+
136
+
137
+ def my_hook(*args):
138
+ """ Function for testing hook
139
+ :param args: Args should be ('pre|post', sshreader.ServerJob)
140
+ :return:
141
+ """
142
+ args = list(args)
143
+ if len(args) == 1:
144
+ if args[0] in ('pre', 'post') and isinstance(args.pop(), sshreader.ServerJob):
145
+ return True
146
+ else:
147
+ return False
148
+ else:
149
+ if args[0] in ('pre', 'post'):
150
+ return True
151
+ else:
152
+ return False
153
+
154
+
155
+ class TestSshreader(unittest.TestCase):
156
+ """ Test cases for the sshreader module
157
+ """
158
+
159
+ @staticmethod
160
+ def configure_serverjob_list(size):
161
+ """ Configure a list of serverjob objects to sshread (including pre and post hooks) and local commands
162
+ :return: List
163
+ """
164
+ global ssh_data
165
+ pre = sshreader.Hook(my_hook, args=['pre'])
166
+ post = sshreader.Hook(my_hook, args=['post'])
167
+ jobs = list()
168
+ for x in range(size):
169
+ x = sshreader.ServerJob(ssh_data['host_fqdn'], ['sleep 1', 'echo done'], pre_hook=pre, post_hook=post,
170
+ username=ssh_data['ssh_user'], password=ssh_data['ssh_password'],
171
+ ssh_port=ssh_data['host_port'])
172
+ jobs.append(x)
173
+ for x in range(size):
174
+ jobs.append(sshreader.ServerJob('local-' + str(x), ['sleep 1', 'echo done'], run_local=True))
175
+ return jobs
176
+
177
+ def test_Hook_creation(self):
178
+ """ Test valid hook creation
179
+ """
180
+ myhook = sshreader.Hook(my_hook, args=['pre'])
181
+ self.assertIsInstance(myhook, sshreader.Hook)
182
+ pass
183
+
184
+ def test_ServerJob(self):
185
+ """ Test valid ServerJob creation
186
+ """
187
+ global ssh_data
188
+ job = sshreader.ServerJob(ssh_data['host_fqdn'], 'echo foo', ssh_port=ssh_data['host_port'],
189
+ username=ssh_data['ssh_user'], password=ssh_data['ssh_password'])
190
+ self.assertIsInstance(job, sshreader.ServerJob)
191
+ pass
192
+
193
+ def test_ServerJob_with_hooks(self):
194
+ """ Test ServerJob with hooks
195
+ """
196
+ global ssh_data
197
+ pre = sshreader.Hook(my_hook, args=['pre'])
198
+ post = sshreader.Hook(my_hook, args=['post'])
199
+ job = sshreader.ServerJob(ssh_data['host_fqdn'], 'echo foo', pre_hook=pre, post_hook=post,
200
+ username=ssh_data['ssh_user'], password=ssh_data['ssh_password'],
201
+ ssh_port=ssh_data['host_port'])
202
+ self.assertIsInstance(job, sshreader.ServerJob)
203
+ pass
204
+
205
+ def test_sshread_threads(self):
206
+ """ Test sshread method using threads
207
+ """
208
+ jobs = self.configure_serverjob_list(1)
209
+ result = sshreader.sshread(jobs, tcount=0)
210
+ for x in result:
211
+ self.assertEqual(x.status, 0, msg=x.results)
212
+ pass
213
+
214
+ def test_sshread_processes(self):
215
+ """ Test sshread method using processes
216
+ """
217
+ jobs = self.configure_serverjob_list(1)
218
+ result = sshreader.sshread(jobs, pcount=0)
219
+ for x in result:
220
+ self.assertEqual(x.status, 0, msg=x.results)
221
+ pass
222
+
223
+ def test_sshread(self):
224
+ """ Test sshread method using threads and processes
225
+ """
226
+ jobs = self.configure_serverjob_list(1)
227
+ result = sshreader.sshread(jobs, pcount=0, tcount=0)
228
+ for x in result:
229
+ self.assertEqual(x.status, 0, msg=x.results)
230
+ pass
231
+
232
+ def test_cpu_limit(self):
233
+ """ Ensure the cpu_limit methods
234
+ """
235
+ self.assertIsInstance(sshreader.utils.cpu_limit(), int)
236
+ self.assertIsInstance(sshreader.utils.cpu_limit(2), int)
237
+ pass
238
+
239
+
240
+ if __name__ == '__main__':
241
+ with warnings.catch_warnings(record=True):
242
+ unittest.main()
@@ -1,53 +0,0 @@
1
- [tool.poetry]
2
- name = "sshreader"
3
- version = "6.0.0"
4
- description = "Multi-threading/processing wrapper for Paramiko"
5
- authors = ["Jesse Almanrode <jesse@almanrode.com>"]
6
- license = "LGPL-3.0-or-later"
7
- readme = "README.md"
8
- homepage = "http://sshreader.readthedocs.io/"
9
- repository = "https://bitbucket.org/isaiah1112/sshreader/"
10
- classifiers = [
11
- "Programming Language :: Python",
12
- "Intended Audience :: Developers",
13
- "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
14
- "Development Status :: 5 - Production/Stable",
15
- "Programming Language :: Python",
16
- "Programming Language :: Python :: 3.8",
17
- "Programming Language :: Python :: 3.9",
18
- "Programming Language :: Python :: 3.10",
19
- "Programming Language :: Python :: 3.11",
20
- "Programming Language :: Python :: 3.12",
21
- "Topic :: Software Development :: Libraries",
22
- "Topic :: Software Development :: Libraries :: Python Modules"
23
- ]
24
- packages = [{include = "sshreader"}, {include = "sshreader/scripts"}]
25
-
26
- [tool.poetry.scripts]
27
- pydsh = 'sshreader.scripts.pydsh:cli'
28
-
29
- [tool.poetry.dependencies]
30
- python = ">3.8,<3.13"
31
- click = "^8"
32
- colorama = "^0.4"
33
- paramiko = "^3"
34
- progressbar2 = "^4"
35
- python-hostlist = "^1"
36
-
37
- [tool.poetry.group.dev]
38
- optional = true
39
-
40
- [tool.poetry.group.dev.dependencies]
41
- coverage = "^7"
42
- discover = "^0.4"
43
- flake8 = "^5"
44
-
45
- [tool.poetry.group.docs]
46
- optional = true
47
-
48
- [tool.poetry.group.docs.dependencies]
49
- sphinx = "^7"
50
-
51
- [build-system]
52
- requires = ["poetry-core"]
53
- build-backend = "poetry.core.masonry.api"
File without changes
File without changes