sshreader 6.1.0__tar.gz → 6.3.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.
- sshreader-6.3.0/PKG-INFO +88 -0
- sshreader-6.3.0/README.md +59 -0
- sshreader-6.3.0/pyproject.toml +86 -0
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/__init__.py +5 -5
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/customtypes.py +3 -4
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/scripts/__init__.py +1 -2
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/scripts/pydsh.py +19 -22
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/ssh.py +17 -20
- {sshreader-6.1.0 → sshreader-6.3.0}/sshreader/utils.py +35 -37
- sshreader-6.3.0/tests/keys/id_rsa +27 -0
- sshreader-6.3.0/tests/keys/id_rsa.pub +1 -0
- sshreader-6.3.0/tests/test_pydsh.py +83 -0
- sshreader-6.3.0/tests/test_sshreader.py +242 -0
- sshreader-6.1.0/PKG-INFO +0 -113
- sshreader-6.1.0/README.md +0 -83
- sshreader-6.1.0/pyproject.toml +0 -53
- {sshreader-6.1.0 → sshreader-6.3.0}/LICENSE +0 -0
- {sshreader-6.1.0 → sshreader-6.3.0}/LICENSE.lesser +0 -0
sshreader-6.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sshreader
|
|
3
|
+
Version: 6.3.0
|
|
4
|
+
Summary: Multi-threading/processing wrapper for Paramiko
|
|
5
|
+
Author-Email: Jesse Almanrode <jesse@almanrode.com>
|
|
6
|
+
License: LGPL-3.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Project-URL: homepage, http://sshreader.readthedocs.io/
|
|
21
|
+
Project-URL: repository, https://bitbucket.org/isaiah1112/sshreader/
|
|
22
|
+
Requires-Python: <4.0,>3.9
|
|
23
|
+
Requires-Dist: click<9,>=8
|
|
24
|
+
Requires-Dist: colorama<1.0,>=0.4
|
|
25
|
+
Requires-Dist: paramiko<4,>=3
|
|
26
|
+
Requires-Dist: progressbar2<5,>=4
|
|
27
|
+
Requires-Dist: python-hostlist<2,>=1
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
[](https://github.com/isaiah1112/sshreader/actions/workflows/lint_and_test.yml)
|
|
33
|
+
|
|
34
|
+
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
|
|
36
|
+
for its ssh client.
|
|
37
|
+
|
|
38
|
+
In order to maintain the widest range of compatibility, [SSHreader][] is currently tested
|
|
39
|
+
using [all supported versions of Python!](https://devguide.python.org/versions/)
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
[SSHreader][] is released under [GNU Lesser General Public License v3.0][],
|
|
44
|
+
see the file LICENSE and LICENSE.lesser for the license text.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
The most straightforward way to get the [SSHreader][] module working for you is:
|
|
49
|
+
|
|
50
|
+
```commandline
|
|
51
|
+
pip install sshreader
|
|
52
|
+
```
|
|
53
|
+
or, if you'd like to use a local install from the repo:
|
|
54
|
+
```commandline
|
|
55
|
+
make install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This ensures that all the requirements are met.
|
|
59
|
+
|
|
60
|
+
## Documentation
|
|
61
|
+
|
|
62
|
+
The documentation for [SSHreader][] can be found at [ReadTheDocs.](https://sshreader.readthedocs.io)
|
|
63
|
+
|
|
64
|
+
## Contributing
|
|
65
|
+
|
|
66
|
+
Comments and enhancements are very welcome.
|
|
67
|
+
|
|
68
|
+
Report any issues or feature requests on the [GitHub Issue Tracker](https://github.com/isaiah1112/sshreader/issues).
|
|
69
|
+
Please include a minimal not-working example which reproduces the bug and, if appropriate, the
|
|
70
|
+
traceback information. Please do not request features already being worked
|
|
71
|
+
towards.
|
|
72
|
+
|
|
73
|
+
Code contributions are encouraged: please feel free to [fork the project](https://github.com/isaiah1112/sshreader)
|
|
74
|
+
and submit pull requests to the develop branch.
|
|
75
|
+
|
|
76
|
+
## Extras
|
|
77
|
+
|
|
78
|
+
Included with sshreader is a script called `pydsh`. This works very similar to [pdsh](https://computing.llnl.gov/linux/pdsh.html)
|
|
79
|
+
but uses sshreader at its core to perform ssh commands in parallel and return the results.
|
|
80
|
+
The output of `pydsh` can also be piped through the `dshbak` tool that comes with pdsh.
|
|
81
|
+
|
|
82
|
+
Pydsh uses [hostlist expressions](https://www.nsc.liu.se/~kent/python-hostlist/) to get its list of hosts
|
|
83
|
+
to process.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
[GNU Lesser General Public License v3.0]: http://choosealicense.com/licenses/lgpl-3.0/ "LGPL v3"
|
|
87
|
+
|
|
88
|
+
[sshreader]: https://github.com/isaiah1112/sshreader "SSHreader Package"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://github.com/isaiah1112/sshreader/actions/workflows/lint_and_test.yml)
|
|
4
|
+
|
|
5
|
+
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
|
|
7
|
+
for its ssh client.
|
|
8
|
+
|
|
9
|
+
In order to maintain the widest range of compatibility, [SSHreader][] is currently tested
|
|
10
|
+
using [all supported versions of Python!](https://devguide.python.org/versions/)
|
|
11
|
+
|
|
12
|
+
## License
|
|
13
|
+
|
|
14
|
+
[SSHreader][] is released under [GNU Lesser General Public License v3.0][],
|
|
15
|
+
see the file LICENSE and LICENSE.lesser for the license text.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
The most straightforward way to get the [SSHreader][] module working for you is:
|
|
20
|
+
|
|
21
|
+
```commandline
|
|
22
|
+
pip install sshreader
|
|
23
|
+
```
|
|
24
|
+
or, if you'd like to use a local install from the repo:
|
|
25
|
+
```commandline
|
|
26
|
+
make install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This ensures that all the requirements are met.
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
The documentation for [SSHreader][] can be found at [ReadTheDocs.](https://sshreader.readthedocs.io)
|
|
34
|
+
|
|
35
|
+
## Contributing
|
|
36
|
+
|
|
37
|
+
Comments and enhancements are very welcome.
|
|
38
|
+
|
|
39
|
+
Report any issues or feature requests on the [GitHub Issue Tracker](https://github.com/isaiah1112/sshreader/issues).
|
|
40
|
+
Please include a minimal not-working example which reproduces the bug and, if appropriate, the
|
|
41
|
+
traceback information. Please do not request features already being worked
|
|
42
|
+
towards.
|
|
43
|
+
|
|
44
|
+
Code contributions are encouraged: please feel free to [fork the project](https://github.com/isaiah1112/sshreader)
|
|
45
|
+
and submit pull requests to the develop branch.
|
|
46
|
+
|
|
47
|
+
## Extras
|
|
48
|
+
|
|
49
|
+
Included with sshreader is a script called `pydsh`. This works very similar to [pdsh](https://computing.llnl.gov/linux/pdsh.html)
|
|
50
|
+
but uses sshreader at its core to perform ssh commands in parallel and return the results.
|
|
51
|
+
The output of `pydsh` can also be piped through the `dshbak` tool that comes with pdsh.
|
|
52
|
+
|
|
53
|
+
Pydsh uses [hostlist expressions](https://www.nsc.liu.se/~kent/python-hostlist/) to get its list of hosts
|
|
54
|
+
to process.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
[GNU Lesser General Public License v3.0]: http://choosealicense.com/licenses/lgpl-3.0/ "LGPL v3"
|
|
58
|
+
|
|
59
|
+
[sshreader]: https://github.com/isaiah1112/sshreader "SSHreader Package"
|
|
@@ -0,0 +1,86 @@
|
|
|
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.3.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
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Software Development :: Libraries",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.license]
|
|
34
|
+
text = "LGPL-3.0-or-later"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
homepage = "http://sshreader.readthedocs.io/"
|
|
38
|
+
repository = "https://bitbucket.org/isaiah1112/sshreader/"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
pydsh = "sshreader.scripts.pydsh:cli"
|
|
42
|
+
|
|
43
|
+
[tool.pdm.build]
|
|
44
|
+
includes = [
|
|
45
|
+
"sshreader",
|
|
46
|
+
"sshreader/scripts",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = [
|
|
51
|
+
"E",
|
|
52
|
+
"F",
|
|
53
|
+
"B",
|
|
54
|
+
"UP",
|
|
55
|
+
"B",
|
|
56
|
+
"SIM",
|
|
57
|
+
"I",
|
|
58
|
+
]
|
|
59
|
+
ignore = [
|
|
60
|
+
"E501",
|
|
61
|
+
"F401",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[tool.uv.index]]
|
|
65
|
+
name = "test-pypi"
|
|
66
|
+
url = "https://test.pypi.org/simple/"
|
|
67
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
68
|
+
explicit = true
|
|
69
|
+
|
|
70
|
+
[build-system]
|
|
71
|
+
requires = [
|
|
72
|
+
"pdm-backend",
|
|
73
|
+
]
|
|
74
|
+
build-backend = "pdm.backend"
|
|
75
|
+
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
test = [
|
|
78
|
+
"coverage<8,>=7",
|
|
79
|
+
"discover<1.0,>=0.4",
|
|
80
|
+
"pytest>=8.4.2",
|
|
81
|
+
"ruff<1.0.0,>=0.9.9",
|
|
82
|
+
"ty>=0.0.5",
|
|
83
|
+
]
|
|
84
|
+
docs = [
|
|
85
|
+
"sphinx<8,>=7",
|
|
86
|
+
]
|
|
@@ -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-
|
|
3
|
+
# Copyright (C) 2015-2026 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-
|
|
3
|
+
# Copyright (C) 2015-2026 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
|
|
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 =
|
|
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-
|
|
3
|
+
# Copyright (C) 2015-2026 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-
|
|
3
|
+
# Copyright (C) 2015-2026 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
|
-
|
|
23
|
+
|
|
22
24
|
import click
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
+
from hostlist import collect_hostlist, expand_hostlist
|
|
26
|
+
|
|
25
27
|
import sshreader
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
# GLOBALS
|
|
28
30
|
__author__ = 'Jesse Almanrode'
|
|
29
|
-
__version__ = '3.
|
|
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
|
|
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__)
|
|
@@ -166,10 +162,11 @@ def cli(**kwargs):
|
|
|
166
162
|
if kwargs['file']:
|
|
167
163
|
mkpath = click.Path(exists=True, dir_okay=False)
|
|
168
164
|
script_path = mkpath(kwargs['cmd'])
|
|
169
|
-
|
|
165
|
+
# Ensure script_path is a str for os.path functions (type-checker safe)
|
|
166
|
+
script_name = os.path.basename(str(script_path))
|
|
170
167
|
log.info('Creating copy_script prehook for: ' + script_name)
|
|
171
168
|
prehook = sshreader.Hook(copy_script, args=[script_path], ssh_established=True)
|
|
172
|
-
with open(script_path) as s:
|
|
169
|
+
with open(str(script_path)) as s:
|
|
173
170
|
script = s.readline()
|
|
174
171
|
if script.startswith('#!') is False:
|
|
175
172
|
raise click.UsageError('Script must start with #!')
|
|
@@ -200,7 +197,7 @@ def cli(**kwargs):
|
|
|
200
197
|
kwargs['keyfile'] = sshenv.dsa_key
|
|
201
198
|
log.info('Using DSA private key file')
|
|
202
199
|
else:
|
|
203
|
-
if len(sshenv.agent_keys) == 0:
|
|
200
|
+
if sshenv.agent_keys is None or len(sshenv.agent_keys) == 0:
|
|
204
201
|
if not all((kwargs['username'], kwargs['password'])):
|
|
205
202
|
raise click.ClickException('Unable to find ssh key to use and password not supplied.')
|
|
206
203
|
else:
|
|
@@ -218,7 +215,7 @@ def cli(**kwargs):
|
|
|
218
215
|
# If you specify a password or prompt for one it overrides the ssh key
|
|
219
216
|
if not kwargs['password']:
|
|
220
217
|
if not kwargs['prompt']:
|
|
221
|
-
if not kwargs['keyfile'] and len(sshenv.agent_keys) == 0:
|
|
218
|
+
if not kwargs['keyfile'] and (sshenv.agent_keys is None or len(sshenv.agent_keys) == 0):
|
|
222
219
|
raise click.ClickException('Unable to find ssh key to use and password not supplied or prompt enabled.')
|
|
223
220
|
else:
|
|
224
221
|
log.info('Prompting for password and disabling discovered SSH keyfiles')
|
|
@@ -255,7 +252,7 @@ def cli(**kwargs):
|
|
|
255
252
|
job.pre_hook = prehook
|
|
256
253
|
jobs.append(job)
|
|
257
254
|
|
|
258
|
-
log.info('Sending
|
|
255
|
+
log.info(f'Sending {len(jobs)} ServerJobs to sshreader module')
|
|
259
256
|
if kwargs['dshbak'] is False and kwargs['coalesce'] is False:
|
|
260
257
|
if kwargs['redline']:
|
|
261
258
|
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-
|
|
3
|
+
# Copyright (C) 2015-2026 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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
@@ -174,7 +173,7 @@ class SSH(object):
|
|
|
174
173
|
else:
|
|
175
174
|
result = Command(cmd=command, stdout=stdout.read().strip(), stderr=None,
|
|
176
175
|
return_code=stdout.channel.recv_exit_status())
|
|
177
|
-
except (paramiko.buffered_pipe.PipeTimeout, socket.timeout):
|
|
176
|
+
except (paramiko.buffered_pipe.PipeTimeout, socket.timeout): # ty:ignore[possibly-missing-submodule]
|
|
178
177
|
result = Command(cmd=command, stdout='command timed out', stderr=None, return_code=124)
|
|
179
178
|
else:
|
|
180
179
|
try:
|
|
@@ -185,7 +184,7 @@ class SSH(object):
|
|
|
185
184
|
else:
|
|
186
185
|
result = Command(cmd=command, stdout=stdout.read().strip(), stderr=stderr.read().strip(),
|
|
187
186
|
return_code=stdout.channel.recv_exit_status())
|
|
188
|
-
except (paramiko.buffered_pipe.PipeTimeout, socket.timeout):
|
|
187
|
+
except (paramiko.buffered_pipe.PipeTimeout, socket.timeout): # ty:ignore[possibly-missing-submodule]
|
|
189
188
|
result = Command(cmd=command, stdout='', stderr='command timed out', return_code=124)
|
|
190
189
|
return result
|
|
191
190
|
|
|
@@ -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
|
|
230
|
-
paramiko.util.logging.getLogger().setLevel(logging.CRITICAL) # Keeping paramiko from logging errors to stdout
|
|
231
|
-
if not self.keyfile:
|
|
232
|
-
|
|
233
|
-
if not all((self.username, self.password)):
|
|
234
|
-
paramiko.SSHException('username and password or keyfile not provided')
|
|
228
|
+
raise paramiko.SSHException(f"connection to {self.host} already established")
|
|
229
|
+
paramiko.util.logging.getLogger().setLevel(logging.CRITICAL) # Keeping paramiko from logging errors to stdout # ty:ignore[possibly-missing-submodule]
|
|
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,
|