gnetcli-adapter 2.5.2__tar.gz → 2.7.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.
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/PKG-INFO +45 -2
- gnetcli_adapter-2.7.0/README.md +45 -0
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/pyproject.toml +1 -1
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/src/gnetcli_adapter/gnetcli_adapter.py +110 -156
- gnetcli_adapter-2.5.2/README.md +0 -2
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/LICENSE +0 -0
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/src/gnetcli_adapter/__init__.py +0 -0
- {gnetcli_adapter-2.5.2 → gnetcli_adapter-2.7.0}/src/gnetcli_adapter/progress_tracker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gnetcli_adapter
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Gnetcli-server adapter for Annet
|
|
5
5
|
Author-email: Aleksandr Balezin <gescheit12@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: exceptiongroup>=1.1.3; python_version<'3.11'
|
|
16
16
|
Requires-Dist: annet>=3.2.0
|
|
17
|
-
Requires-Dist: gnetclisdk>=1.0
|
|
17
|
+
Requires-Dist: gnetclisdk>=1.1.0
|
|
18
18
|
Requires-Dist: pydantic_settings
|
|
19
19
|
Requires-Dist: pydantic
|
|
20
20
|
Requires-Dist: bandit[toml]==1.7.5 ; extra == "test"
|
|
@@ -42,3 +42,46 @@ Provides-Extra: test
|
|
|
42
42
|
# gnetcli_adapter
|
|
43
43
|
This package provides deployer and fetcher adapters for Annet
|
|
44
44
|
|
|
45
|
+
# Examples
|
|
46
|
+
|
|
47
|
+
## Using specified login and password
|
|
48
|
+
|
|
49
|
+
cat ~/.annet/context.yml
|
|
50
|
+
```yaml
|
|
51
|
+
fetcher:
|
|
52
|
+
default:
|
|
53
|
+
adapter: gnetcli
|
|
54
|
+
params: &gnetcli
|
|
55
|
+
dev_login: mylogin
|
|
56
|
+
dev_password: mypassword
|
|
57
|
+
deployer:
|
|
58
|
+
default:
|
|
59
|
+
adapter: gnetcli
|
|
60
|
+
params:
|
|
61
|
+
<<: *gnetcli
|
|
62
|
+
...
|
|
63
|
+
context:
|
|
64
|
+
default:
|
|
65
|
+
fetcher: default
|
|
66
|
+
deployer: default
|
|
67
|
+
selected_context: default
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Using tunnel through master SSH-connection
|
|
71
|
+
|
|
72
|
+
https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
|
|
73
|
+
|
|
74
|
+
cat ~/.ssh/context.yml
|
|
75
|
+
```
|
|
76
|
+
Host myhost*
|
|
77
|
+
ProxyJump mybastion
|
|
78
|
+
|
|
79
|
+
Host mybastion
|
|
80
|
+
ControlMaster auto
|
|
81
|
+
ControlPath ~/.ssh/mastersockets/%r@%h:%p
|
|
82
|
+
ControlPersist 120m
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`~/.annet/context.yml` the same because gnetcli read .ssh/config by default.
|
|
86
|
+
|
|
87
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# gnetcli_adapter
|
|
2
|
+
This package provides deployer and fetcher adapters for Annet
|
|
3
|
+
|
|
4
|
+
# Examples
|
|
5
|
+
|
|
6
|
+
## Using specified login and password
|
|
7
|
+
|
|
8
|
+
cat ~/.annet/context.yml
|
|
9
|
+
```yaml
|
|
10
|
+
fetcher:
|
|
11
|
+
default:
|
|
12
|
+
adapter: gnetcli
|
|
13
|
+
params: &gnetcli
|
|
14
|
+
dev_login: mylogin
|
|
15
|
+
dev_password: mypassword
|
|
16
|
+
deployer:
|
|
17
|
+
default:
|
|
18
|
+
adapter: gnetcli
|
|
19
|
+
params:
|
|
20
|
+
<<: *gnetcli
|
|
21
|
+
...
|
|
22
|
+
context:
|
|
23
|
+
default:
|
|
24
|
+
fetcher: default
|
|
25
|
+
deployer: default
|
|
26
|
+
selected_context: default
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Using tunnel through master SSH-connection
|
|
30
|
+
|
|
31
|
+
https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
|
|
32
|
+
|
|
33
|
+
cat ~/.ssh/context.yml
|
|
34
|
+
```
|
|
35
|
+
Host myhost*
|
|
36
|
+
ProxyJump mybastion
|
|
37
|
+
|
|
38
|
+
Host mybastion
|
|
39
|
+
ControlMaster auto
|
|
40
|
+
ControlPath ~/.ssh/mastersockets/%r@%h:%p
|
|
41
|
+
ControlPersist 120m
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`~/.annet/context.yml` the same because gnetcli read .ssh/config by default.
|
|
45
|
+
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import abc
|
|
1
2
|
import traceback
|
|
2
3
|
|
|
3
4
|
import asyncio
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import base64
|
|
6
|
+
from collections.abc import AsyncIterator, Iterable
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
10
|
|
|
8
11
|
import annet.annlib.command
|
|
9
12
|
|
|
@@ -12,26 +15,21 @@ from annet.annlib.command import Command, CommandList
|
|
|
12
15
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
13
16
|
from annet.adapters.netbox.common.models import NetboxDevice
|
|
14
17
|
from annet.rulebook import common
|
|
15
|
-
|
|
16
18
|
from annet.connectors import AdapterWithConfig, AdapterWithName
|
|
17
|
-
from typing import Dict, List, Any, Optional, Tuple
|
|
18
19
|
from annet.storage import Device
|
|
19
|
-
|
|
20
|
-
from
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
from gnetclisdk.client import Credentials, Gnetcli, HostParams, QA, File, GnetcliSessionCmd
|
|
20
|
+
from gnetclisdk.config import Config
|
|
21
|
+
from gnetclisdk.starter import DEFAULT_GNETCLI_SERVER_CONF, GnetcliStarter
|
|
22
|
+
from gnetclisdk.client import Credentials, Gnetcli, HostParams, QA, File
|
|
24
23
|
from gnetclisdk.exceptions import EOFError
|
|
25
24
|
import gnetclisdk.proto.server_pb2 as pb
|
|
26
25
|
from pydantic import Field, field_validator, FieldValidationInfo
|
|
27
26
|
from pydantic_core import PydanticUndefined
|
|
28
27
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
29
|
-
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
import os.path
|
|
28
|
+
|
|
29
|
+
from gnetcli_adapter.progress_tracker import (
|
|
30
|
+
ProgressTracker, ProgressBarTracker, FileProgressTracker, LogProgressTracker, CompositeTracker,
|
|
31
|
+
)
|
|
32
|
+
|
|
35
33
|
try:
|
|
36
34
|
from builtins import ( # type: ignore[attr-defined, unused-ignore]
|
|
37
35
|
ExceptionGroup,
|
|
@@ -55,23 +53,9 @@ breed_to_device = {
|
|
|
55
53
|
"vrp55": "huawei",
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
logging:
|
|
60
|
-
level: debug
|
|
61
|
-
json: true
|
|
62
|
-
port: 0
|
|
63
|
-
dev_auth:
|
|
64
|
-
use_agent: true
|
|
65
|
-
ssh_config: true
|
|
66
|
-
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
_local_gnetcli: Optional[threading.Thread] = None
|
|
70
|
-
_local_gnetcli_p: Optional[subprocess.Popen] = None
|
|
71
|
-
_local_gnetcli_url: Optional[str] = None
|
|
72
|
-
LOG_FORMAT = "%(asctime)s - l:%(lineno)d - %(funcName)s() - %(levelname)s - %(message)s"
|
|
73
|
-
DATE_FMT = "%Y-%m-%d %H:%M:%S"
|
|
56
|
+
|
|
74
57
|
DEFAULT_GNETCLI_SERVER_PATH = "gnetcli_server"
|
|
58
|
+
|
|
75
59
|
_logger = logging.getLogger(__name__)
|
|
76
60
|
|
|
77
61
|
|
|
@@ -80,7 +64,7 @@ class AppSettings(BaseSettings):
|
|
|
80
64
|
|
|
81
65
|
url: Optional[str] = None
|
|
82
66
|
server_path: str = DEFAULT_GNETCLI_SERVER_PATH
|
|
83
|
-
server_conf:
|
|
67
|
+
server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF
|
|
84
68
|
insecure_grpc: bool = Field(default=True)
|
|
85
69
|
login: Optional[str] = None
|
|
86
70
|
password: Optional[str] = None
|
|
@@ -128,29 +112,6 @@ async def get_config(breed: str) -> List[str]:
|
|
|
128
112
|
raise Exception("unknown breed %r" % breed)
|
|
129
113
|
|
|
130
114
|
|
|
131
|
-
def check_gnetcli_server(server_path: str, config: str = DEFAULT_GNETCLI_SERVER_CONF):
|
|
132
|
-
global _local_gnetcli
|
|
133
|
-
if not _local_gnetcli:
|
|
134
|
-
t = threading.Thread(target=run_gnetcli_server,
|
|
135
|
-
kwargs={"server_path": server_path, "config": config})
|
|
136
|
-
t.daemon = True
|
|
137
|
-
t.start()
|
|
138
|
-
time.sleep(1)
|
|
139
|
-
_local_gnetcli = t
|
|
140
|
-
if _local_gnetcli_p is None:
|
|
141
|
-
raise Exception("server failed")
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def cleanup():
|
|
145
|
-
if _local_gnetcli_p is not None:
|
|
146
|
-
_local_gnetcli_p.terminate()
|
|
147
|
-
time.sleep(0.05)
|
|
148
|
-
_local_gnetcli_p.kill()
|
|
149
|
-
time.sleep(0.05)
|
|
150
|
-
|
|
151
|
-
atexit.register(cleanup)
|
|
152
|
-
|
|
153
|
-
|
|
154
115
|
async def gather_with_concurrency(n: int, *coros: list[asyncio.Task]):
|
|
155
116
|
if n == 0:
|
|
156
117
|
return await asyncio.gather(*coros, return_exceptions=True)
|
|
@@ -182,54 +143,21 @@ def get_device_ip(dev: Device) -> Optional[str]:
|
|
|
182
143
|
_logger.warning("get device ip error: %s", e)
|
|
183
144
|
return None
|
|
184
145
|
|
|
146
|
+
class ApiMaker(metaclass=abc.ABCMeta):
|
|
147
|
+
conf: AppSettings
|
|
148
|
+
|
|
149
|
+
@asynccontextmanager
|
|
150
|
+
async def make_api(self) -> AsyncIterator[Gnetcli]:
|
|
151
|
+
async with GnetcliStarter(self.conf.server_path, self.conf.server_conf) as gnetcli_url:
|
|
152
|
+
yield Gnetcli(
|
|
153
|
+
server=gnetcli_url,
|
|
154
|
+
auth_token=self.conf.make_server_credentials(),
|
|
155
|
+
insecure_grpc=self.conf.insecure_grpc,
|
|
156
|
+
user_agent="annet",
|
|
157
|
+
)
|
|
185
158
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
global _local_gnetcli_url
|
|
189
|
-
abs_path: Optional[str] = shutil.which(server_path)
|
|
190
|
-
if not abs_path and server_path == DEFAULT_GNETCLI_SERVER_PATH:
|
|
191
|
-
abs_path = shutil.which(os.path.expanduser("~/go/bin/gnetcli_server"))
|
|
192
|
-
gnetcli_abs_path: str = abs_path or server_path
|
|
193
|
-
_logger.info("starting gnetcli server %s", gnetcli_abs_path)
|
|
194
|
-
try:
|
|
195
|
-
proc = subprocess.Popen(
|
|
196
|
-
[gnetcli_abs_path, "--conf-file", "-"],
|
|
197
|
-
stdout=subprocess.DEVNULL, # we do not read stdout
|
|
198
|
-
stderr=subprocess.PIPE,
|
|
199
|
-
stdin=subprocess.PIPE,
|
|
200
|
-
bufsize=1,
|
|
201
|
-
universal_newlines=True,
|
|
202
|
-
)
|
|
203
|
-
except Exception as e:
|
|
204
|
-
logging.exception("server exec error %s", e)
|
|
205
|
-
raise
|
|
206
|
-
proc.stdin.write(config)
|
|
207
|
-
proc.stdin.close()
|
|
208
|
-
_local_gnetcli_p = proc
|
|
209
|
-
while True:
|
|
210
|
-
output = proc.stderr.readline()
|
|
211
|
-
if output == "" and proc.poll() is not None:
|
|
212
|
-
break
|
|
213
|
-
if output:
|
|
214
|
-
_logger.debug("gnetcli output: %s", output.strip())
|
|
215
|
-
if _local_gnetcli_url is None:
|
|
216
|
-
try:
|
|
217
|
-
data = json.loads(output)
|
|
218
|
-
except Exception:
|
|
219
|
-
pass
|
|
220
|
-
else:
|
|
221
|
-
if data.get("msg") == "init tcp socket":
|
|
222
|
-
_local_gnetcli_url = data.get("address")
|
|
223
|
-
if data.get("msg") == "init unix socket":
|
|
224
|
-
_local_gnetcli_url = "unix:" + data.get("path")
|
|
225
|
-
if data.get("level") == "panic":
|
|
226
|
-
_logger.error("gnetcli error %s", data)
|
|
227
|
-
_logger.debug("set gnetcli server exit code %s", proc.returncode)
|
|
228
|
-
rc = proc.poll()
|
|
229
|
-
return rc
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
159
|
+
|
|
160
|
+
class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName, ApiMaker):
|
|
233
161
|
def __init__(
|
|
234
162
|
self,
|
|
235
163
|
url: Optional[str] = None,
|
|
@@ -239,7 +167,7 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
239
167
|
dev_password: Optional[str] = None,
|
|
240
168
|
ssh_agent_enabled: bool = True,
|
|
241
169
|
server_path: Optional[str] = None,
|
|
242
|
-
server_conf:
|
|
170
|
+
server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF,
|
|
243
171
|
):
|
|
244
172
|
conf_args = {
|
|
245
173
|
"login": login,
|
|
@@ -252,7 +180,6 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
252
180
|
"ssh_agent_enabled": ssh_agent_enabled,
|
|
253
181
|
}
|
|
254
182
|
self.conf = AppSettings(**{k: v for k,v in conf_args.items() if v is not None})
|
|
255
|
-
self.api = make_api(self.conf)
|
|
256
183
|
|
|
257
184
|
@classmethod
|
|
258
185
|
def name(cls) -> str:
|
|
@@ -268,11 +195,25 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
268
195
|
# TODO: implement fetch_packages
|
|
269
196
|
return {}, {}
|
|
270
197
|
|
|
271
|
-
async def fetch(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
198
|
+
async def fetch(
|
|
199
|
+
self,
|
|
200
|
+
devices: List[Device],
|
|
201
|
+
files_to_download: Optional[Dict[Device, List[str]]] = None,
|
|
202
|
+
processes: int = 1,
|
|
203
|
+
max_slots: int = 0,
|
|
204
|
+
):
|
|
205
|
+
if not devices:
|
|
206
|
+
return {}, {}
|
|
207
|
+
async with self.make_api() as api:
|
|
208
|
+
return await self._fetch(api, devices, files_to_download, processes, max_slots)
|
|
209
|
+
|
|
210
|
+
async def _fetch(
|
|
211
|
+
self,
|
|
212
|
+
api: Gnetcli,
|
|
213
|
+
devices: List[Device],
|
|
214
|
+
files_to_download: Optional[Dict[Device, List[str]]] = None,
|
|
215
|
+
processes: int = 1,
|
|
216
|
+
max_slots: int = 0,
|
|
276
217
|
):
|
|
277
218
|
running = {}
|
|
278
219
|
failed_running = {}
|
|
@@ -282,12 +223,12 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
282
223
|
if files_to_download or device.is_pc():
|
|
283
224
|
if files_to_download:
|
|
284
225
|
files = files_to_download.get(device, [])
|
|
285
|
-
task = self.adownload_dev(device=device, files=files)
|
|
226
|
+
task = self.adownload_dev(api=api, device=device, files=files)
|
|
286
227
|
tasks[task] = device
|
|
287
228
|
else:
|
|
288
229
|
running[device] = {}
|
|
289
230
|
else:
|
|
290
|
-
task = self.afetch_dev(device=device)
|
|
231
|
+
task = self.afetch_dev(api=api, device=device)
|
|
291
232
|
tasks[task] = device
|
|
292
233
|
|
|
293
234
|
if not tasks:
|
|
@@ -306,14 +247,14 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
306
247
|
running[device] = dev_res
|
|
307
248
|
return running, failed_running
|
|
308
249
|
|
|
309
|
-
async def afetch_dev(self, device: Device) -> str:
|
|
250
|
+
async def afetch_dev(self, api: Gnetcli, device: Device) -> str:
|
|
310
251
|
cmds = await get_config(breed=device.breed)
|
|
311
252
|
# map annet breed to gnetcli device type
|
|
312
253
|
gnetcli_device = breed_to_device.get(device.breed, device.breed)
|
|
313
254
|
dev_result = []
|
|
314
255
|
ip = get_device_ip(device)
|
|
315
256
|
for cmd in cmds:
|
|
316
|
-
res = await
|
|
257
|
+
res = await api.cmd(
|
|
317
258
|
hostname=device.fqdn,
|
|
318
259
|
cmd=cmd,
|
|
319
260
|
host_params=HostParams(
|
|
@@ -327,10 +268,10 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
327
268
|
dev_result.append(res.out)
|
|
328
269
|
return b"\n".join(dev_result).decode()
|
|
329
270
|
|
|
330
|
-
async def adownload_dev(self, device: Device, files: List[str]) -> Dict[str, str]:
|
|
271
|
+
async def adownload_dev(self, api: Gnetcli, device: Device, files: List[str]) -> Dict[str, str]:
|
|
331
272
|
gnetcli_device = breed_to_device.get(device.breed, device.breed)
|
|
332
273
|
ip = get_device_ip(device)
|
|
333
|
-
downloaded = await
|
|
274
|
+
downloaded = await api.download(
|
|
334
275
|
hostname=device.fqdn,
|
|
335
276
|
paths=files,
|
|
336
277
|
host_params=HostParams(
|
|
@@ -355,30 +296,7 @@ def parse_annet_qa(qa: list[annet.annlib.command.Question]) -> list[QA]:
|
|
|
355
296
|
return res
|
|
356
297
|
|
|
357
298
|
|
|
358
|
-
|
|
359
|
-
gnetcli_url = _local_gnetcli_url
|
|
360
|
-
if not conf.url:
|
|
361
|
-
check_gnetcli_server(server_path=conf.server_path, config=conf.server_conf)
|
|
362
|
-
if _local_gnetcli_url is None:
|
|
363
|
-
_logger.info("waiting for _local_gnetcli_url appears")
|
|
364
|
-
start = time.monotonic()
|
|
365
|
-
while time.monotonic() - start < 5:
|
|
366
|
-
if _local_gnetcli_p is not None and _local_gnetcli_p.returncode is not None:
|
|
367
|
-
raise Exception("gnetcli server died with code %s" % _local_gnetcli_p.returncode)
|
|
368
|
-
gnetcli_url = _local_gnetcli_url
|
|
369
|
-
else:
|
|
370
|
-
gnetcli_url = conf.url
|
|
371
|
-
auth_token = conf.make_server_credentials()
|
|
372
|
-
api = Gnetcli(
|
|
373
|
-
server=gnetcli_url,
|
|
374
|
-
auth_token=auth_token,
|
|
375
|
-
insecure_grpc=conf.insecure_grpc,
|
|
376
|
-
user_agent="annet",
|
|
377
|
-
)
|
|
378
|
-
return api
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
299
|
+
class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName, ApiMaker):
|
|
382
300
|
def __init__(
|
|
383
301
|
self,
|
|
384
302
|
url: Optional[str] = None,
|
|
@@ -388,7 +306,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
388
306
|
dev_password: Optional[str] = None,
|
|
389
307
|
ssh_agent_enabled: bool = True,
|
|
390
308
|
server_path: Optional[str] = None,
|
|
391
|
-
server_conf: Optional[
|
|
309
|
+
server_conf: Optional[Config] = DEFAULT_GNETCLI_SERVER_CONF,
|
|
392
310
|
logs_dir: Optional[str] = None,
|
|
393
311
|
):
|
|
394
312
|
conf_args = {
|
|
@@ -402,7 +320,6 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
402
320
|
"ssh_agent_enabled": ssh_agent_enabled,
|
|
403
321
|
}
|
|
404
322
|
self.conf = AppSettings(**{k: v for k,v in conf_args.items() if v is not None})
|
|
405
|
-
self.api = make_api(self.conf)
|
|
406
323
|
self.logs_dir = logs_dir
|
|
407
324
|
|
|
408
325
|
@classmethod
|
|
@@ -413,7 +330,29 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
413
330
|
def with_config(cls, **kwargs: Any) -> DeployDriver:
|
|
414
331
|
return cls(**kwargs)
|
|
415
332
|
|
|
416
|
-
async def bulk_deploy(
|
|
333
|
+
async def bulk_deploy(
|
|
334
|
+
self,
|
|
335
|
+
deploy_cmds: dict[Device, CommandList],
|
|
336
|
+
args: DeployOptions,
|
|
337
|
+
progress_bar: ProgressBar | None = None,
|
|
338
|
+
) -> DeployResult:
|
|
339
|
+
if not deploy_cmds:
|
|
340
|
+
return DeployResult(hostnames=[], results={}, durations={}, original_states={})
|
|
341
|
+
async with self.make_api() as api:
|
|
342
|
+
return await self._bulk_deploy(
|
|
343
|
+
api=api,
|
|
344
|
+
deploy_cmds=deploy_cmds,
|
|
345
|
+
args=args,
|
|
346
|
+
progress_bar=progress_bar,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
async def _bulk_deploy(
|
|
350
|
+
self,
|
|
351
|
+
api: Gnetcli,
|
|
352
|
+
deploy_cmds: dict[Device, CommandList],
|
|
353
|
+
args: DeployOptions,
|
|
354
|
+
progress_bar: ProgressBar | None = None,
|
|
355
|
+
) -> DeployResult:
|
|
417
356
|
if progress_bar:
|
|
418
357
|
for host, cmds in deploy_cmds.items():
|
|
419
358
|
progress_bar.set_progress(host.fqdn, 0, len(cmds))
|
|
@@ -427,11 +366,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
427
366
|
pass
|
|
428
367
|
deploy_items = deploy_cmds.items()
|
|
429
368
|
if max_parallel == 1:
|
|
430
|
-
result = await self.serial_deploy(deploy_items, args, progress_bar)
|
|
369
|
+
result = await self.serial_deploy(api, deploy_items, args, progress_bar)
|
|
431
370
|
else:
|
|
432
371
|
result = await gather_with_concurrency(
|
|
433
372
|
max_parallel,
|
|
434
|
-
*[self.deploy(device, cmds, args, progress_bar) for device, cmds in deploy_items],
|
|
373
|
+
*[self.deploy(api, device, cmds, args, progress_bar) for device, cmds in deploy_items],
|
|
435
374
|
)
|
|
436
375
|
res = DeployResult(hostnames=[], results={}, durations={}, original_states={})
|
|
437
376
|
res.add_results(results={dev.fqdn: dev_res for (dev, _), dev_res in zip(deploy_items, result)})
|
|
@@ -452,7 +391,9 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
452
391
|
return reload_cmds
|
|
453
392
|
|
|
454
393
|
def _get_total(
|
|
455
|
-
|
|
394
|
+
self,
|
|
395
|
+
command_groups: list[tuple[str, CommandList]],
|
|
396
|
+
files: dict[str, File],
|
|
456
397
|
) -> int:
|
|
457
398
|
run_cmds = 0
|
|
458
399
|
for _, cmds in command_groups:
|
|
@@ -461,7 +402,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
461
402
|
run_cmds += 1
|
|
462
403
|
return run_cmds
|
|
463
404
|
|
|
464
|
-
def _init_progress_tracker(
|
|
405
|
+
def _init_progress_tracker(
|
|
406
|
+
self,
|
|
407
|
+
device: Device,
|
|
408
|
+
progress_bar: ProgressBar | None,
|
|
409
|
+
) -> ProgressTracker:
|
|
465
410
|
tracker = CompositeTracker(LogProgressTracker(device))
|
|
466
411
|
if progress_bar:
|
|
467
412
|
tracker.add_tracker(ProgressBarTracker(device, progress_bar))
|
|
@@ -469,7 +414,13 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
469
414
|
tracker.add_tracker(FileProgressTracker(device, self.logs_dir))
|
|
470
415
|
return tracker
|
|
471
416
|
|
|
472
|
-
async def serial_deploy(
|
|
417
|
+
async def serial_deploy(
|
|
418
|
+
self,
|
|
419
|
+
api: Gnetcli,
|
|
420
|
+
deploy_items: Iterable[tuple[Device, CommandList]],
|
|
421
|
+
args: DeployOptions,
|
|
422
|
+
progress_bar: ProgressBar | None = None,
|
|
423
|
+
):
|
|
473
424
|
res = {}
|
|
474
425
|
for device, cmds in deploy_items:
|
|
475
426
|
if progress_bar:
|
|
@@ -481,16 +432,17 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
481
432
|
break
|
|
482
433
|
except Exception:
|
|
483
434
|
pass
|
|
484
|
-
dev_res = await self.deploy(device, cmds, args, progress_bar)
|
|
435
|
+
dev_res = await self.deploy(api, device, cmds, args, progress_bar)
|
|
485
436
|
res[device] = dev_res
|
|
486
437
|
return res
|
|
487
438
|
|
|
488
439
|
async def deploy(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
440
|
+
self,
|
|
441
|
+
api: Gnetcli,
|
|
442
|
+
device: Device,
|
|
443
|
+
cmds: CommandList,
|
|
444
|
+
args: DeployOptions,
|
|
445
|
+
progress_bar: ProgressBar | None = None,
|
|
494
446
|
) -> Exception | None:
|
|
495
447
|
gnetcli_device = breed_to_device.get(device.breed, device.breed)
|
|
496
448
|
ip = get_device_ip(device)
|
|
@@ -515,6 +467,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
515
467
|
tracker.set_total(self._get_total(command_groups, files))
|
|
516
468
|
try:
|
|
517
469
|
seen_exc, results = await self._deploy(
|
|
470
|
+
api=api,
|
|
518
471
|
device=device,
|
|
519
472
|
host_params=host_params,
|
|
520
473
|
command_groups=command_groups,
|
|
@@ -537,6 +490,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
537
490
|
|
|
538
491
|
async def _deploy(
|
|
539
492
|
self,
|
|
493
|
+
api: Gnetcli,
|
|
540
494
|
device: Device,
|
|
541
495
|
host_params: HostParams,
|
|
542
496
|
command_groups: list[tuple[str, CommandList]],
|
|
@@ -547,11 +501,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
547
501
|
results = []
|
|
548
502
|
if files:
|
|
549
503
|
tracker.upload_files(list(files))
|
|
550
|
-
await
|
|
504
|
+
await api.upload(hostname=device.fqdn, files=files, host_params=host_params)
|
|
551
505
|
tracker.files_uploaded()
|
|
552
506
|
|
|
553
507
|
old_group_name = ""
|
|
554
|
-
async with
|
|
508
|
+
async with api.cmd_session(hostname=device.fqdn) as session:
|
|
555
509
|
for group_number, (group_name, cmdlist) in enumerate(command_groups):
|
|
556
510
|
if group_name != old_group_name:
|
|
557
511
|
tracker.start_group(group_name)
|
gnetcli_adapter-2.5.2/README.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|