cloudru-qa-ssh-client 1.0.0__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.
- cloudru_qa_ssh_client-1.0.0.dist-info/METADATA +492 -0
- cloudru_qa_ssh_client-1.0.0.dist-info/RECORD +21 -0
- cloudru_qa_ssh_client-1.0.0.dist-info/WHEEL +4 -0
- cloudru_qa_ssh_client-1.0.0.dist-info/licenses/LICENSE +202 -0
- ssh_client/__init__.py +18 -0
- ssh_client/client.py +357 -0
- ssh_client/clients/__init__.py +0 -0
- ssh_client/clients/asyncssh_client.py +155 -0
- ssh_client/clients/exceptions.py +29 -0
- ssh_client/clients/paramiko_client.py +190 -0
- ssh_client/config.py +36 -0
- ssh_client/event_hooks.py +112 -0
- ssh_client/examples/__init__.py +0 -0
- ssh_client/examples/basic_usage.py +83 -0
- ssh_client/options.py +208 -0
- ssh_client/py.typed +0 -0
- ssh_client/results.py +30 -0
- ssh_client/types.py +80 -0
- ssh_client/utils/__init__.py +9 -0
- ssh_client/utils/masking.py +42 -0
- ssh_client/utils/retries.py +178 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cloudru-qa-ssh-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Простой SSH-клиент для автотестов
|
|
5
|
+
Project-URL: Repository, https://github.com/cloud-ru-tech/qa-libraries
|
|
6
|
+
Project-URL: Issues, https://github.com/cloud-ru-tech/qa-libraries/issues
|
|
7
|
+
Author-email: akfernatii <akfernatii@cloud.ru>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Topic :: System :: Networking
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.13
|
|
19
|
+
Requires-Dist: asyncssh<3,>=2.14.0
|
|
20
|
+
Requires-Dist: paramiko<5.2,>=5.1.0
|
|
21
|
+
Provides-Extra: structlog
|
|
22
|
+
Requires-Dist: structlog>=26.1.0; extra == 'structlog'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# SSH client
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/cloudru-qa-ssh-client/)
|
|
28
|
+
[](https://pypi.org/project/cloudru-qa-ssh-client/)
|
|
29
|
+
[](https://github.com/cloud-ru-tech/qa-libraries/blob/master/LICENSE)
|
|
30
|
+
|
|
31
|
+
English | [Русский](README.ru.md)
|
|
32
|
+
|
|
33
|
+
A simple SSH client library for automated tests: synchronous and asynchronous clients with
|
|
34
|
+
constructor options, command decorators, and logging.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Requires Python 3.13 or later.
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
pip install cloudru-qa-ssh-client
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For structured logging with structlog, install the `structlog` extra:
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
pip install "cloudru-qa-ssh-client[structlog]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Import the library as the module `ssh_client`.
|
|
51
|
+
|
|
52
|
+
## Skills for AI agents
|
|
53
|
+
|
|
54
|
+
This library does not have its own skill yet. If your project uses other
|
|
55
|
+
libraries from this suite with [skills for AI agents](https://github.com/cloud-ru-tech/qa-libraries#skills-for-ai-agents),
|
|
56
|
+
select and enable them from your project root (requires [`uv`](https://docs.astral.sh/uv/)):
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
uvx library-skills
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For Claude Code, add `--claude`.
|
|
63
|
+
|
|
64
|
+
## Main features
|
|
65
|
+
|
|
66
|
+
1. Connect to remote hosts using a password or a key
|
|
67
|
+
2. Run commands as strings or lists of shell command fragments
|
|
68
|
+
3. Set timeouts for the client and individual requests
|
|
69
|
+
4. Check exit codes with `raise_for_status=True` for individual commands
|
|
70
|
+
5. Configure retry counts and intervals
|
|
71
|
+
6. Retry connection and command failures
|
|
72
|
+
7. Get result objects with stdout, stderr, and exit code fields
|
|
73
|
+
8. Log requests and responses with a supplied logger
|
|
74
|
+
9. Mask request and response logs using strings or regular expressions
|
|
75
|
+
10. Use asynchronous clients through asyncssh
|
|
76
|
+
11. Receive output in real time for interactive commands
|
|
77
|
+
12. Interrupt commands with conditional callbacks
|
|
78
|
+
|
|
79
|
+
## Usage example
|
|
80
|
+
|
|
81
|
+
### Synchronous client
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import logging
|
|
85
|
+
from ssh_client import SyncSSHClient, options
|
|
86
|
+
|
|
87
|
+
# Create a logger
|
|
88
|
+
logger = logging.getLogger(__name__)
|
|
89
|
+
logger.setLevel(logging.INFO)
|
|
90
|
+
|
|
91
|
+
# Create a client
|
|
92
|
+
client = SyncSSHClient(
|
|
93
|
+
"example.com",
|
|
94
|
+
22,
|
|
95
|
+
options.with_password("user", "password"),
|
|
96
|
+
options.with_request_timeout(30),
|
|
97
|
+
options.with_logger(logger),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Run a command
|
|
101
|
+
result = client.execute(["ls", "-la"])
|
|
102
|
+
print(f"Exit code: {result.exit_code}")
|
|
103
|
+
print(f"Stdout: {result.stdout}")
|
|
104
|
+
print(f"Stderr: {result.stderr}")
|
|
105
|
+
|
|
106
|
+
# Close the connection
|
|
107
|
+
client.close()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Asynchronous client
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import asyncio
|
|
114
|
+
import logging
|
|
115
|
+
from ssh_client import AsyncSSHClient, options
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def main():
|
|
119
|
+
# Create a logger
|
|
120
|
+
logger = logging.getLogger(__name__)
|
|
121
|
+
logger.setLevel(logging.INFO)
|
|
122
|
+
|
|
123
|
+
# Create a client
|
|
124
|
+
client = AsyncSSHClient(
|
|
125
|
+
"example.com",
|
|
126
|
+
22,
|
|
127
|
+
options.with_private_key("user", "/path/to/private/key"),
|
|
128
|
+
options.with_request_timeout(30),
|
|
129
|
+
options.with_logger(logger),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Run a command
|
|
133
|
+
result = await client.execute(["ls", "-la"])
|
|
134
|
+
print(f"Exit code: {result.exit_code}")
|
|
135
|
+
print(f"Stdout: {result.stdout}")
|
|
136
|
+
print(f"Stderr: {result.stderr}")
|
|
137
|
+
|
|
138
|
+
# Close the connection
|
|
139
|
+
await client.aclose()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Run the asynchronous function
|
|
143
|
+
asyncio.run(main())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Configuration
|
|
147
|
+
|
|
148
|
+
Configure the client using `ClientOption` options. Built-in options are in the `options` module.
|
|
149
|
+
|
|
150
|
+
By default, both clients verify server keys against the standard `~/.ssh/known_hosts` file.
|
|
151
|
+
Unknown or changed keys are rejected. To use a separate file, set
|
|
152
|
+
`options.with_known_hosts("/path/to/known_hosts")`; the file must contain a previously verified server key.
|
|
153
|
+
|
|
154
|
+
### Available options
|
|
155
|
+
|
|
156
|
+
* `with_request_timeout`: set the SSH request timeout
|
|
157
|
+
* `with_connection_timeout`: set the SSH connection timeout
|
|
158
|
+
* `with_password`: set the username and password for authentication
|
|
159
|
+
* `with_private_key`: set the username and private key path for authentication
|
|
160
|
+
* `with_known_hosts`: choose a trusted server keys file
|
|
161
|
+
* `with_logger`: set a logger for SSH requests and responses
|
|
162
|
+
* `with_structlogger`: set a structured logger for SSH requests and responses (requires the structlog extra)
|
|
163
|
+
* `retry_if_connection_fails`: retry failed connections
|
|
164
|
+
* `retry_if_command_fails`: retry failed commands
|
|
165
|
+
* `with_connect_decorators`: add custom decorators to the connection method
|
|
166
|
+
* `with_execute_decorators`: add custom decorators to the command execution method
|
|
167
|
+
* `with_live_execute_callback`: add callbacks to process stdout and stderr lines in real time
|
|
168
|
+
|
|
169
|
+
### Using with_live_execute_callback
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import logging
|
|
173
|
+
from ssh_client import SyncSSHClient, options
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Define callbacks for live output
|
|
177
|
+
def stdout_callback(line):
|
|
178
|
+
print(f"STDOUT: {line}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def stderr_callback(line):
|
|
182
|
+
print(f"STDERR: {line}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Create a client with callbacks
|
|
186
|
+
client = SyncSSHClient(
|
|
187
|
+
"example.com",
|
|
188
|
+
22,
|
|
189
|
+
options.with_password("user", "password"),
|
|
190
|
+
options.with_live_execute_callback(stdout_callback, stderr_callback),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Run a command with live output
|
|
194
|
+
result = client.execute_live(["ping", "-c", "3", "google.com"])
|
|
195
|
+
|
|
196
|
+
# Callbacks print all lines in real time
|
|
197
|
+
print(f"Exit code: {result.exit_code}")
|
|
198
|
+
|
|
199
|
+
# Close the connection
|
|
200
|
+
client.close()
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Interrupting commands with callbacks
|
|
204
|
+
|
|
205
|
+
Interrupt a command when a condition is met. This is useful for long-running commands that should stop when particular text appears in the output.
|
|
206
|
+
|
|
207
|
+
#### Interrupting on stdout (synchronous client)
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
import logging
|
|
211
|
+
from ssh_client import SyncSSHClient, options
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Define a conditional interrupt callback
|
|
215
|
+
def interrupt_on_keyword(line):
|
|
216
|
+
"""Interrupt execution if the line contains 'ERROR'."""
|
|
217
|
+
print(f"Processing line: {line}")
|
|
218
|
+
return "ERROR" in line
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Create a client
|
|
222
|
+
client = SyncSSHClient(
|
|
223
|
+
"example.com",
|
|
224
|
+
22,
|
|
225
|
+
options.with_password("user", "password"),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
# Run a command with conditional interruption
|
|
230
|
+
result = client.execute_live(
|
|
231
|
+
["tail", "-f", "/var/log/application.log"], interrupt_stdout_callback=interrupt_on_keyword
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
print(f"Command was interrupted with exit code: {result.exit_code}")
|
|
235
|
+
print("Captured output:")
|
|
236
|
+
for line in result.stdout_lines:
|
|
237
|
+
print(f" {line}")
|
|
238
|
+
finally:
|
|
239
|
+
client.close()
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Interrupting on stderr (asynchronous client)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
import asyncio
|
|
246
|
+
from ssh_client import AsyncSSHClient, options
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def main():
|
|
250
|
+
# Define a stderr interrupt callback
|
|
251
|
+
def interrupt_on_error(line):
|
|
252
|
+
"""Interrupt execution if stderr contains a critical error."""
|
|
253
|
+
print(f"Error detected: {line}")
|
|
254
|
+
return "CRITICAL" in line or "FATAL" in line
|
|
255
|
+
|
|
256
|
+
# Create a client
|
|
257
|
+
client = AsyncSSHClient(
|
|
258
|
+
"example.com",
|
|
259
|
+
22,
|
|
260
|
+
options.with_private_key("user", "/path/to/private/key"),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Run a command with stderr interruption
|
|
265
|
+
result = await client.execute_live(["some-long-running-command"], interrupt_stderr_callback=interrupt_on_error)
|
|
266
|
+
|
|
267
|
+
print(f"Command was interrupted with exit code: {result.exit_code}")
|
|
268
|
+
print("Captured stderr:")
|
|
269
|
+
for line in result.stderr_lines:
|
|
270
|
+
print(f" {line}")
|
|
271
|
+
finally:
|
|
272
|
+
await client.aclose()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Run the asynchronous function
|
|
276
|
+
asyncio.run(main())
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Synchronous `execute_live` merges stdout and stderr through a terminal. Separate stderr callbacks are available on the asynchronous client.
|
|
280
|
+
|
|
281
|
+
When an interrupt callback fires, the command stops and `execute_live` returns a result with a negative exit code (-1 for the synchronous client, -2 for the asynchronous client), including all lines received before the interruption.
|
|
282
|
+
|
|
283
|
+
## Advanced features
|
|
284
|
+
|
|
285
|
+
### Temporary options with with_options
|
|
286
|
+
|
|
287
|
+
Use `with_options` to create temporary clients with additional options for a single command:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
import logging
|
|
291
|
+
from ssh_client import SyncSSHClient, options
|
|
292
|
+
|
|
293
|
+
# Create the base client
|
|
294
|
+
client = SyncSSHClient(
|
|
295
|
+
"example.com",
|
|
296
|
+
22,
|
|
297
|
+
options.with_password("user", "password"),
|
|
298
|
+
options.with_request_timeout(30),
|
|
299
|
+
options.with_logger(logging.getLogger(__name__)),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Use with_options for a command with custom settings
|
|
303
|
+
result = client.with_options(
|
|
304
|
+
options.with_request_timeout(60), # Longer timeout for this command only
|
|
305
|
+
options.retry_if_command_fails(
|
|
306
|
+
retries_attempts_number=3, retries_interval_in_seconds=1
|
|
307
|
+
), # Retries for this command only
|
|
308
|
+
).execute(["sleep", "10", "&&", "echo", "Done"])
|
|
309
|
+
|
|
310
|
+
# The base client stays unchanged
|
|
311
|
+
client.close()
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Structured logging
|
|
315
|
+
|
|
316
|
+
Install the `structlog` extra to use structured logging:
|
|
317
|
+
|
|
318
|
+
```shell
|
|
319
|
+
pip install "cloudru-qa-ssh-client[structlog]"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Structured logging example:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
import structlog
|
|
326
|
+
from ssh_client import SyncSSHClient, options
|
|
327
|
+
|
|
328
|
+
# Configure structlog
|
|
329
|
+
structlog.configure(
|
|
330
|
+
processors=[structlog.processors.TimeStamper(fmt="iso"), structlog.processors.JSONRenderer()],
|
|
331
|
+
context_class=dict,
|
|
332
|
+
logger_factory=structlog.PrintLoggerFactory(),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Create a client with a structured logger
|
|
336
|
+
client = SyncSSHClient(
|
|
337
|
+
"example.com",
|
|
338
|
+
22,
|
|
339
|
+
options.with_password("user", "password"),
|
|
340
|
+
options.with_structlogger(structlog.get_logger()),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Run a command with structured logging
|
|
344
|
+
result = client.execute(["ls", "-la"])
|
|
345
|
+
print(f"Exit code: {result.exit_code}")
|
|
346
|
+
|
|
347
|
+
# Close the connection
|
|
348
|
+
client.close()
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Retries
|
|
352
|
+
|
|
353
|
+
Configure retries for both connections and command execution:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from ssh_client import SyncSSHClient, options
|
|
357
|
+
|
|
358
|
+
client = SyncSSHClient(
|
|
359
|
+
"unstable-server.com",
|
|
360
|
+
22,
|
|
361
|
+
options.with_password("user", "password"),
|
|
362
|
+
# Connection retries
|
|
363
|
+
options.retry_if_connection_fails(
|
|
364
|
+
retries_attempts_number=5, retries_interval_in_seconds=10.0, reraise_exception=True
|
|
365
|
+
),
|
|
366
|
+
# Command retries
|
|
367
|
+
options.retry_if_command_fails(retries_attempts_number=3, retries_interval_in_seconds=5.0, reraise_exception=True),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Run a command with retries
|
|
371
|
+
result = client.execute(["some-unstable-command"])
|
|
372
|
+
|
|
373
|
+
# Close the connection
|
|
374
|
+
client.close()
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Masking log data
|
|
378
|
+
|
|
379
|
+
Mask confidential data in logs:
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
import logging
|
|
383
|
+
import re
|
|
384
|
+
from ssh_client import SyncSSHClient, options
|
|
385
|
+
|
|
386
|
+
# Configure a logger
|
|
387
|
+
logger = logging.getLogger(__name__)
|
|
388
|
+
logger.setLevel(logging.INFO)
|
|
389
|
+
|
|
390
|
+
client = SyncSSHClient(
|
|
391
|
+
"example.com",
|
|
392
|
+
22,
|
|
393
|
+
options.with_password("user", "password"),
|
|
394
|
+
# Mask confidential log data
|
|
395
|
+
options.with_logger(
|
|
396
|
+
logger,
|
|
397
|
+
mask_patterns=[
|
|
398
|
+
"password", # String to replace
|
|
399
|
+
"secret", # Another string to replace
|
|
400
|
+
re.compile(r"token=\w+"), # Regular expression
|
|
401
|
+
],
|
|
402
|
+
),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Run a command with confidential data
|
|
406
|
+
# The data is masked in logs
|
|
407
|
+
result = client.execute(["echo", "password=secret123", "token=abc123def456"])
|
|
408
|
+
|
|
409
|
+
# Close the connection
|
|
410
|
+
client.close()
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Custom decorators
|
|
414
|
+
|
|
415
|
+
Add custom decorators to connection and command execution methods:
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
import time
|
|
419
|
+
from functools import wraps
|
|
420
|
+
from ssh_client import SyncSSHClient, options
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# Decorator to measure execution time
|
|
424
|
+
def timing_decorator(func):
|
|
425
|
+
@wraps(func)
|
|
426
|
+
def wrapper(*args, **kwargs):
|
|
427
|
+
start_time = time.time()
|
|
428
|
+
result = func(*args, **kwargs)
|
|
429
|
+
end_time = time.time()
|
|
430
|
+
print(f"{func.__name__} took {end_time - start_time:.2f} seconds")
|
|
431
|
+
return result
|
|
432
|
+
|
|
433
|
+
return wrapper
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# Decorator to handle errors
|
|
437
|
+
def error_handler_decorator(func):
|
|
438
|
+
@wraps(func)
|
|
439
|
+
def wrapper(*args, **kwargs):
|
|
440
|
+
try:
|
|
441
|
+
return func(*args, **kwargs)
|
|
442
|
+
except Exception as e:
|
|
443
|
+
print(f"Error in {func.__name__}: {e}")
|
|
444
|
+
# Retry or take another action here
|
|
445
|
+
raise
|
|
446
|
+
|
|
447
|
+
return wrapper
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
client = SyncSSHClient(
|
|
451
|
+
"example.com",
|
|
452
|
+
22,
|
|
453
|
+
options.with_password("user", "password"),
|
|
454
|
+
# Add connection decorators
|
|
455
|
+
options.with_connect_decorators(timing_decorator, error_handler_decorator),
|
|
456
|
+
# Add command execution decorators
|
|
457
|
+
options.with_execute_decorators(timing_decorator),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Run a command with decorators
|
|
461
|
+
result = client.execute(["ls", "-la"])
|
|
462
|
+
|
|
463
|
+
# Close the connection
|
|
464
|
+
client.close()
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Development
|
|
468
|
+
|
|
469
|
+
This library is developed in the [qa-libraries](https://github.com/cloud-ru-tech/qa-libraries) monorepo
|
|
470
|
+
alongside the other `cloudru-qa-*` libraries. You need [`uv`](https://docs.astral.sh/uv/), GNU Make
|
|
471
|
+
and [`checkmake`](https://github.com/checkmake/checkmake);
|
|
472
|
+
run the commands from the repository root:
|
|
473
|
+
|
|
474
|
+
```shell
|
|
475
|
+
make sync # dependencies for all libraries from uv.lock
|
|
476
|
+
make test-ssh-client # tests for this library
|
|
477
|
+
make typecheck-ssh-client # mypy for this library
|
|
478
|
+
make check # formatting, Ruff, mypy, and tests for the whole suite
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Tests require Docker to be running: the SSH server starts in a container automatically. Server
|
|
482
|
+
and client keys are generated for each run and are not stored in the repository.
|
|
483
|
+
|
|
484
|
+
Tests run on Python 3.13 and 3.14. Each version uses a separate environment:
|
|
485
|
+
`make sync test-ssh-client PYTHON_VERSION=3.14`.
|
|
486
|
+
|
|
487
|
+
Create a release with the Git tag `ssh-client/v<version>`. The tag version must match the version
|
|
488
|
+
in `packages/ssh-client/pyproject.toml`.
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
[Apache-2.0](https://github.com/cloud-ru-tech/qa-libraries/blob/master/LICENSE)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
ssh_client/__init__.py,sha256=USdulJKVyGRgxgQaltU_gVISv9ZxGzAGOX6IhGsXLfQ,473
|
|
2
|
+
ssh_client/client.py,sha256=pkvpU-tMipDl0dEU8ICI8i_c15IXmKiVxkis0-0maQk,11419
|
|
3
|
+
ssh_client/config.py,sha256=lKz-nVMTV8-Xxd2N7yk3I3RYztnFRNtVqACe36JmGR4,1113
|
|
4
|
+
ssh_client/event_hooks.py,sha256=voDafOJXYtfH6oi2HqIFXcFv2XyP6SzrAlSEq-jIFIE,3601
|
|
5
|
+
ssh_client/options.py,sha256=m-Sb-cEprz8sG82rX7RjH_ls78YSFK12nPc18JEmW5k,6080
|
|
6
|
+
ssh_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
ssh_client/results.py,sha256=UL8_7w5M9XXhswvEF4TSbzVdA27PHzIExnX5jj3Uh18,896
|
|
8
|
+
ssh_client/types.py,sha256=CllecsyNY91UJb2bV8L8fnOMTNPoT_fH6dWLTeUns9k,2204
|
|
9
|
+
ssh_client/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
ssh_client/clients/asyncssh_client.py,sha256=dYdNaJeXNCqR-OqsKqHnTdgCGHRrbI9JZsZ_ZDttbBs,6705
|
|
11
|
+
ssh_client/clients/exceptions.py,sha256=-iohyrEYqjcAwMd2KTRXMJXGZolliZYxa7v5RstYPzM,1035
|
|
12
|
+
ssh_client/clients/paramiko_client.py,sha256=CfzW27U_0HYHN6ASaAimTOfn9NkiiCUqqGU1jHg_IKM,7927
|
|
13
|
+
ssh_client/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
ssh_client/examples/basic_usage.py,sha256=AxRTnTN4EpqTzleyy_mz_eXiT_XKINfyC7gzQ6GYOdM,3076
|
|
15
|
+
ssh_client/utils/__init__.py,sha256=ERR092hr_in0zmtazHQQQjl279nlZoaNMbujw8Y-0YI,259
|
|
16
|
+
ssh_client/utils/masking.py,sha256=oacicWLdAObiSLUW61M38K9iF9_1q3nEYAbu3pD7k6k,1455
|
|
17
|
+
ssh_client/utils/retries.py,sha256=sjJWCVXQM7zTzNQe2ktB8bYVAjXDUbzrrLfnv93y5o4,6237
|
|
18
|
+
cloudru_qa_ssh_client-1.0.0.dist-info/METADATA,sha256=vpc5Kjs8nwiE5AO2vpKQ3z3b_3wk84KET_IXJjldTEQ,14166
|
|
19
|
+
cloudru_qa_ssh_client-1.0.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
20
|
+
cloudru_qa_ssh_client-1.0.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
21
|
+
cloudru_qa_ssh_client-1.0.0.dist-info/RECORD,,
|