gnetcli-adapter 2.0.5__tar.gz → 2.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.
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/PKG-INFO +2 -2
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/pyproject.toml +1 -1
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/src/gnetcli_adapter/gnetcli_adapter.py +136 -88
- gnetcli_adapter-2.2.0/src/gnetcli_adapter/progress_tracker.py +296 -0
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/LICENSE +0 -0
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/README.md +0 -0
- {gnetcli_adapter-2.0.5 → gnetcli_adapter-2.2.0}/src/gnetcli_adapter/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gnetcli_adapter
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Gnetcli-server adapter for Annet
|
|
5
5
|
Author-email: Aleksandr Balezin <gescheit12@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: annet>=
|
|
15
|
+
Requires-Dist: annet>=2.2.0
|
|
16
16
|
Requires-Dist: gnetclisdk>=1.0.31
|
|
17
17
|
Requires-Dist: pydantic_settings
|
|
18
18
|
Requires-Dist: pydantic
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import json
|
|
3
5
|
import subprocess
|
|
@@ -14,7 +16,11 @@ from annet.rulebook import common
|
|
|
14
16
|
from annet.connectors import AdapterWithConfig, AdapterWithName
|
|
15
17
|
from typing import Dict, List, Any, Optional, Tuple
|
|
16
18
|
from annet.storage import Device
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
from gnetcli_adapter.progress_tracker import (
|
|
21
|
+
ProgressTracker, ProgressBarTracker, FileProgressTracker, LogProgressTracker, CompositeTracker,
|
|
22
|
+
)
|
|
23
|
+
from gnetclisdk.client import Credentials, Gnetcli, HostParams, QA, File, GnetcliSessionCmd
|
|
18
24
|
from gnetclisdk.exceptions import EOFError
|
|
19
25
|
import gnetclisdk.proto.server_pb2 as pb
|
|
20
26
|
from pydantic import Field, field_validator, FieldValidationInfo
|
|
@@ -156,7 +162,7 @@ def run_gnetcli_server(server_path: str, config: str = DEFAULT_GNETCLI_SERVER_CO
|
|
|
156
162
|
try:
|
|
157
163
|
proc = subprocess.Popen(
|
|
158
164
|
[gnetcli_abs_path, "--conf-file", "-"],
|
|
159
|
-
stdout=subprocess.
|
|
165
|
+
stdout=subprocess.DEVNULL, # we do not read stdout
|
|
160
166
|
stderr=subprocess.PIPE,
|
|
161
167
|
stdin=subprocess.PIPE,
|
|
162
168
|
bufsize=1,
|
|
@@ -182,6 +188,8 @@ def run_gnetcli_server(server_path: str, config: str = DEFAULT_GNETCLI_SERVER_CO
|
|
|
182
188
|
else:
|
|
183
189
|
if data.get("msg") == "init tcp socket":
|
|
184
190
|
_local_gnetcli_url = data.get("address")
|
|
191
|
+
if data.get("msg") == "init unix socket":
|
|
192
|
+
_local_gnetcli_url = "unix:" + data.get("path")
|
|
185
193
|
if data.get("level") == "panic":
|
|
186
194
|
_logger.error("gnetcli error %s", data)
|
|
187
195
|
_logger.debug("set gnetcli server exit code %s", proc.returncode)
|
|
@@ -219,7 +227,7 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
|
|
|
219
227
|
return "gnetcli"
|
|
220
228
|
|
|
221
229
|
@classmethod
|
|
222
|
-
def with_config(cls, **kwargs:
|
|
230
|
+
def with_config(cls, **kwargs: Any) -> Fetcher:
|
|
223
231
|
return cls(**kwargs)
|
|
224
232
|
|
|
225
233
|
async def fetch_packages(self, devices: List[Device], processes: int = 1, max_slots: int = 0):
|
|
@@ -333,16 +341,6 @@ def make_api(conf: AppSettings) -> Gnetcli:
|
|
|
333
341
|
)
|
|
334
342
|
return api
|
|
335
343
|
|
|
336
|
-
def format_trace(trace: list[pb.CMDTraceItem]) -> str:
|
|
337
|
-
res:list[str] = []
|
|
338
|
-
for t in trace:
|
|
339
|
-
op = "unknown" # TODO: get from pb
|
|
340
|
-
if t.operation == 2:
|
|
341
|
-
op = "write"
|
|
342
|
-
elif t.operation == 3:
|
|
343
|
-
op = "read"
|
|
344
|
-
res.append(f"{op}={t.data}")
|
|
345
|
-
return "\n".join(res)
|
|
346
344
|
|
|
347
345
|
class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
348
346
|
def __init__(
|
|
@@ -355,6 +353,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
355
353
|
ssh_agent_enabled: bool = True,
|
|
356
354
|
server_path: Optional[str] = None,
|
|
357
355
|
server_conf: Optional[str] = DEFAULT_GNETCLI_SERVER_CONF,
|
|
356
|
+
logs_dir: Optional[str] = None,
|
|
358
357
|
):
|
|
359
358
|
conf_args = {
|
|
360
359
|
"login": login,
|
|
@@ -368,14 +367,15 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
368
367
|
}
|
|
369
368
|
self.conf = AppSettings(**{k: v for k,v in conf_args.items() if v is not None})
|
|
370
369
|
self.api = make_api(self.conf)
|
|
370
|
+
self.logs_dir = logs_dir
|
|
371
371
|
|
|
372
372
|
@classmethod
|
|
373
373
|
def name(cls) -> str:
|
|
374
374
|
return "gnetcli"
|
|
375
375
|
|
|
376
376
|
@classmethod
|
|
377
|
-
def with_config(cls, **kwargs:
|
|
378
|
-
return
|
|
377
|
+
def with_config(cls, **kwargs: Any) -> DeployDriver:
|
|
378
|
+
return cls(**kwargs)
|
|
379
379
|
|
|
380
380
|
async def bulk_deploy(self, deploy_cmds: dict[Device, CommandList], args: DeployOptions, progress_bar: ProgressBar | None = None) -> DeployResult:
|
|
381
381
|
if progress_bar:
|
|
@@ -387,7 +387,45 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
387
387
|
res.add_results(results={dev.fqdn: dev_res for (dev, _), dev_res in zip(deploy_items, result)})
|
|
388
388
|
return res
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
def _get_files(self, cmds: dict[str, Any]) -> dict[str, File]:
|
|
391
|
+
return {
|
|
392
|
+
file: File(content=content, status=None)
|
|
393
|
+
for file, content in cmds["files"].items()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
def _get_reload_cmds(self, cmds: dict[str, Any]):
|
|
397
|
+
reload_cmds: dict[str, CommandList] = {}
|
|
398
|
+
for file, cmd in cmds["cmds"].items():
|
|
399
|
+
if isinstance(cmd, bytes):
|
|
400
|
+
cmd = cmd.decode()
|
|
401
|
+
reload_cmds[file] = CommandList([Command(cmd, suppress_nonzero=True) for cmd in cmd.splitlines()])
|
|
402
|
+
return reload_cmds
|
|
403
|
+
|
|
404
|
+
def _get_total(
|
|
405
|
+
self, command_groups: list[tuple[str, CommandList]], files: dict[str, File],
|
|
406
|
+
) -> int:
|
|
407
|
+
run_cmds = 0
|
|
408
|
+
for _, cmds in command_groups:
|
|
409
|
+
run_cmds += len(cmds)
|
|
410
|
+
if files:
|
|
411
|
+
run_cmds += 1
|
|
412
|
+
return run_cmds
|
|
413
|
+
|
|
414
|
+
def _init_progress_tracker(self, device: Device, progress_bar: ProgressBar | None) -> ProgressTracker:
|
|
415
|
+
tracker = CompositeTracker(LogProgressTracker(device))
|
|
416
|
+
if progress_bar:
|
|
417
|
+
tracker.add_tracker(ProgressBarTracker(device, progress_bar))
|
|
418
|
+
if self.logs_dir:
|
|
419
|
+
tracker.add_tracker(FileProgressTracker(device, self.logs_dir))
|
|
420
|
+
return tracker
|
|
421
|
+
|
|
422
|
+
async def deploy(
|
|
423
|
+
self,
|
|
424
|
+
device: Device,
|
|
425
|
+
cmds: CommandList,
|
|
426
|
+
args: DeployOptions,
|
|
427
|
+
progress_bar: ProgressBar | None = None,
|
|
428
|
+
) -> tuple[list[Exception], list[pb.CMDResult]]:
|
|
391
429
|
gnetcli_device = breed_to_device.get(device.breed, device.breed)
|
|
392
430
|
ip = get_device_ip(device)
|
|
393
431
|
host_params = HostParams(
|
|
@@ -395,85 +433,95 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
|
|
|
395
433
|
device=gnetcli_device,
|
|
396
434
|
ip=ip,
|
|
397
435
|
)
|
|
398
|
-
|
|
399
|
-
seen_exc: list[Exception] = []
|
|
400
|
-
reload_cmds: dict[str, CommandList] = {}
|
|
401
|
-
total_cmds = 0
|
|
436
|
+
command_groups: list[tuple[str, CommandList]]= []
|
|
402
437
|
if isinstance(cmds, dict): # PC
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
total_cmds += len(reload_cmds[file])
|
|
408
|
-
run_cmds = CommandList()
|
|
409
|
-
files: Dict[str, File] = {file: File(content=content, status=None) for file, content in cmds["files"].items()}
|
|
410
|
-
await self.api.upload(hostname=device.fqdn, files=files, host_params=host_params)
|
|
438
|
+
files = self._get_files(cmds)
|
|
439
|
+
if args.entire_reload.value == "yes":
|
|
440
|
+
for file, cmdlist in self._get_reload_cmds(cmds).items():
|
|
441
|
+
command_groups.append((f"Reload {file}", cmdlist))
|
|
411
442
|
else:
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
443
|
+
files = {}
|
|
444
|
+
# treat each command as a group
|
|
445
|
+
for cmd in cmds:
|
|
446
|
+
command_groups.append(("Run command", CommandList([cmd])))
|
|
447
|
+
|
|
448
|
+
with self._init_progress_tracker(device, progress_bar) as tracker:
|
|
449
|
+
tracker.set_total(self._get_total(command_groups, files))
|
|
450
|
+
try:
|
|
451
|
+
seen_exc, results = await self._deploy(
|
|
452
|
+
device=device,
|
|
453
|
+
host_params=host_params,
|
|
454
|
+
command_groups=command_groups,
|
|
455
|
+
tracker=tracker,
|
|
456
|
+
files=files,
|
|
457
|
+
)
|
|
458
|
+
except Exception as e:
|
|
459
|
+
trace = traceback.format_exc()
|
|
460
|
+
tracker.command_done_error(trace)
|
|
461
|
+
seen_exc = [e]
|
|
462
|
+
results = []
|
|
463
|
+
if seen_exc:
|
|
464
|
+
tracker.finish_err(f"Seen {len(seen_exc)} exceptions")
|
|
465
|
+
else:
|
|
466
|
+
tracker.finish_ok("All done")
|
|
467
|
+
return seen_exc, results
|
|
468
|
+
|
|
469
|
+
async def _deploy(
|
|
470
|
+
self,
|
|
471
|
+
device: Device,
|
|
472
|
+
host_params: HostParams,
|
|
473
|
+
command_groups: list[tuple[str, CommandList]],
|
|
474
|
+
files: dict[str, File],
|
|
475
|
+
tracker: ProgressTracker,
|
|
476
|
+
) -> tuple[list[Exception], list[pb.CMDResult]]:
|
|
477
|
+
seen_exc = []
|
|
478
|
+
results = []
|
|
479
|
+
if files:
|
|
480
|
+
tracker.upload_files(list(files))
|
|
481
|
+
await self.api.upload(hostname=device.fqdn, files=files, host_params=host_params)
|
|
482
|
+
tracker.files_uploaded()
|
|
483
|
+
|
|
484
|
+
old_group_name = ""
|
|
485
|
+
async with self.api.cmd_session(hostname=device.fqdn) as session:
|
|
486
|
+
for group_number, (group_name, cmdlist) in enumerate(command_groups):
|
|
487
|
+
if group_name != old_group_name:
|
|
488
|
+
tracker.start_group(group_name)
|
|
489
|
+
old_group_name = group_name
|
|
490
|
+
for cmd_number, cmd in enumerate(cmdlist):
|
|
491
|
+
tracker.run_command(cmd.cmd)
|
|
492
|
+
try:
|
|
493
|
+
res = await session.cmd(
|
|
455
494
|
cmd=cmd.cmd,
|
|
456
495
|
cmd_timeout=cmd.timeout,
|
|
457
496
|
host_params=host_params,
|
|
458
497
|
qa=parse_annet_qa(cmd.questions or []),
|
|
498
|
+
trace=True,
|
|
459
499
|
)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
500
|
+
except EOFError as e:
|
|
501
|
+
# we can't execute subsequent commands
|
|
502
|
+
if cmd.suppress_eof:
|
|
503
|
+
# some commands left
|
|
504
|
+
if group_number + 1 != len(command_groups) or cmd_number + 1 != len(cmd):
|
|
505
|
+
tracker.command_done_error("EOF detected before all commands executed.")
|
|
506
|
+
seen_exc.append(Exception("EOF detected before all commands executed."))
|
|
507
|
+
tracker.command_done_error("Suppressed EOF")
|
|
508
|
+
return seen_exc, results
|
|
509
|
+
seen_exc.append(e)
|
|
510
|
+
tracker.command_done_error("Unexpected EOFError")
|
|
511
|
+
return seen_exc, results
|
|
512
|
+
if res.status == 0:
|
|
513
|
+
tracker.command_done_ok(res)
|
|
514
|
+
results.append(res)
|
|
515
|
+
else:
|
|
516
|
+
e = Exception("cmd %s error %s status %s", cmd, res.error, res.status)
|
|
517
|
+
seen_exc.append(e)
|
|
518
|
+
if cmd.suppress_nonzero:
|
|
519
|
+
tracker.command_done_ok(res)
|
|
520
|
+
break # go to next command group
|
|
521
|
+
else:
|
|
522
|
+
tracker.command_done_error(res.error.decode())
|
|
523
|
+
return seen_exc, results
|
|
524
|
+
return seen_exc, results
|
|
477
525
|
|
|
478
526
|
def apply_deploy_rulebook(
|
|
479
527
|
self,
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from contextlib import ExitStack
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TextIO
|
|
8
|
+
|
|
9
|
+
import gnetclisdk.proto.server_pb2 as pb
|
|
10
|
+
from annet.deploy import ProgressBar
|
|
11
|
+
from annet.storage import Device
|
|
12
|
+
|
|
13
|
+
logger = getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProgressTracker:
|
|
17
|
+
def __enter__(self) -> "ProgressTracker":
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def start_group(self, group_name: str) -> None:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def set_total(self, total: int) -> None:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def upload_files(self, files: list[str]) -> None:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def files_uploaded(self) -> None:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def run_command(self, cmd: str) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def run_reload_command(self, file: str, cmd: str):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def command_done_ok(self, output: pb.CMDResult) -> None:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def command_done_error(self, error: str) -> None:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def finish_ok(self, notification: str) -> None:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def finish_err(self, notification: str) -> None:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def format_trace(trace: list[pb.CMDTraceItem]) -> str:
|
|
55
|
+
res: list[str] = []
|
|
56
|
+
for t in trace:
|
|
57
|
+
op = "unknown" # TODO: get from pb
|
|
58
|
+
if t.operation == 2:
|
|
59
|
+
op = "write"
|
|
60
|
+
elif t.operation == 3:
|
|
61
|
+
op = "read"
|
|
62
|
+
res.append(f"{op}={t.data}")
|
|
63
|
+
return "\n".join(res)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _render_cmd_res(res: pb.CMDResult) -> str:
|
|
67
|
+
if res.trace:
|
|
68
|
+
trace_str = "\n" + format_trace(res.trace)
|
|
69
|
+
else:
|
|
70
|
+
trace_str = ""
|
|
71
|
+
try:
|
|
72
|
+
error_str = res.error.decode("utf-8")
|
|
73
|
+
except UnicodeDecodeError:
|
|
74
|
+
error_str = str(res.error)
|
|
75
|
+
if error_str:
|
|
76
|
+
error_str = "\n" + error_str
|
|
77
|
+
return f"out: {res.out_str}\nstatus: {res.status}{error_str}{trace_str}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ProgressBarTracker(ProgressTracker):
|
|
81
|
+
def __init__(self, device: Device, progress_bar: ProgressBar) -> None:
|
|
82
|
+
self.progress_bar = progress_bar
|
|
83
|
+
self.tile = device.fqdn
|
|
84
|
+
self.total_steps = 0
|
|
85
|
+
self.done_steps = 0
|
|
86
|
+
self.last_cmd = ""
|
|
87
|
+
|
|
88
|
+
def set_total(self, total: int) -> None:
|
|
89
|
+
self.total_steps = total
|
|
90
|
+
|
|
91
|
+
def start_group(self, group_name: str) -> None:
|
|
92
|
+
self.progress_bar.add_content(self.tile, f"=== {group_name} ===")
|
|
93
|
+
|
|
94
|
+
def upload_files(self, files: list[str]) -> None:
|
|
95
|
+
filelist = "".join(f"- {f}\n" for f in files)
|
|
96
|
+
self.progress_bar.add_content(self.tile, f"=== Uploading files ===\n{filelist}")
|
|
97
|
+
|
|
98
|
+
def files_uploaded(self) -> None:
|
|
99
|
+
self.done_steps += 1
|
|
100
|
+
self.progress_bar.add_content(self.tile, f"=== Files uploaded ===\n")
|
|
101
|
+
self.progress_bar.set_progress(self.tile, self.done_steps, self.total_steps)
|
|
102
|
+
|
|
103
|
+
def run_command(self, cmd: str):
|
|
104
|
+
self.progress_bar.add_content(self.tile, f"cmd: {cmd}")
|
|
105
|
+
self.progress_bar.set_progress(
|
|
106
|
+
self.tile,
|
|
107
|
+
self.done_steps,
|
|
108
|
+
self.total_steps,
|
|
109
|
+
cmd,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def run_reload_command(self, file: str, cmd: str):
|
|
113
|
+
self.last_cmd = cmd
|
|
114
|
+
self.progress_bar.add_content(self.tile, f"{file}: {cmd}")
|
|
115
|
+
self.progress_bar.set_progress(self.tile, self.done_steps, self.total_steps, suffix=cmd)
|
|
116
|
+
|
|
117
|
+
def command_done_ok(self, output: pb.CMDResult) -> None:
|
|
118
|
+
self.done_steps += 1
|
|
119
|
+
output_str = _render_cmd_res(output)
|
|
120
|
+
self.progress_bar.add_content(self.tile, output_str)
|
|
121
|
+
self.progress_bar.set_progress(
|
|
122
|
+
self.tile,
|
|
123
|
+
self.done_steps,
|
|
124
|
+
self.total_steps,
|
|
125
|
+
"Ok: " + self.last_cmd,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def command_done_error(self, error: str) -> None:
|
|
129
|
+
logger.error(error)
|
|
130
|
+
self.done_steps += 1
|
|
131
|
+
self.progress_bar.add_content(self.tile, error)
|
|
132
|
+
self.progress_bar.set_progress(
|
|
133
|
+
self.tile,
|
|
134
|
+
self.done_steps,
|
|
135
|
+
self.total_steps,
|
|
136
|
+
"Error: " + self.last_cmd,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def finish_ok(self, notification: str) -> None:
|
|
140
|
+
self.done_steps = self.total_steps
|
|
141
|
+
self.progress_bar.set_progress(self.tile,self.done_steps, self.total_steps, suffix=notification)
|
|
142
|
+
|
|
143
|
+
def finish_err(self, notification: str) -> None:
|
|
144
|
+
self.done_steps = self.total_steps
|
|
145
|
+
self.progress_bar.set_exception(self.tile, notification, "", self.total_steps)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class FileProgressTracker(ProgressTracker):
|
|
149
|
+
def __init__(self, device: Device, dirname: str):
|
|
150
|
+
self.dirname = dirname
|
|
151
|
+
self.device = device
|
|
152
|
+
self.file: TextIO | None = None
|
|
153
|
+
|
|
154
|
+
def __enter__(self) -> ProgressTracker:
|
|
155
|
+
filepath = self._make_file_path()
|
|
156
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
157
|
+
logger.info("Writing deploy logs to file: %s", filepath)
|
|
158
|
+
self.file = open(filepath, "a")
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def _make_file_path(self) -> str:
|
|
162
|
+
now = datetime.now()
|
|
163
|
+
datedir = f"{now:%Y-%m-%d-%H-%M}"
|
|
164
|
+
return os.path.join(
|
|
165
|
+
self.dirname,
|
|
166
|
+
datedir,
|
|
167
|
+
f"{self.device.fqdn}_{now.timestamp():.0f}",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
171
|
+
self.file.close()
|
|
172
|
+
|
|
173
|
+
def _log(self, msg: str) -> None:
|
|
174
|
+
self.file.write(f"{datetime.now()}: {msg}\n")
|
|
175
|
+
|
|
176
|
+
def start_group(self, group_name: str) -> None:
|
|
177
|
+
self._log(f"=== {group_name} ===")
|
|
178
|
+
|
|
179
|
+
def set_total(self, total: int) -> None:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
def upload_files(self, files: list[str]) -> None:
|
|
183
|
+
filelist = "\n".join(f"- {f}" for f in files)
|
|
184
|
+
self._log(f"=== Uploading files ===\n{filelist}")
|
|
185
|
+
|
|
186
|
+
def files_uploaded(self) -> None:
|
|
187
|
+
self._log(f"=== Files uploaded ===")
|
|
188
|
+
|
|
189
|
+
def run_command(self, cmd: str) -> None:
|
|
190
|
+
self._log(f"cmd: {cmd}")
|
|
191
|
+
|
|
192
|
+
def run_reload_command(self, file: str, cmd: str):
|
|
193
|
+
self._log(f"file: {cmd}")
|
|
194
|
+
|
|
195
|
+
def command_done_ok(self, output: pb.CMDResult) -> None:
|
|
196
|
+
output_str = _render_cmd_res(output)
|
|
197
|
+
self._log(output_str)
|
|
198
|
+
|
|
199
|
+
def command_done_error(self, error: str) -> None:
|
|
200
|
+
self._log(f"Error: {error}")
|
|
201
|
+
|
|
202
|
+
def finish_ok(self, notification: str) -> None:
|
|
203
|
+
self._log("Finished with success: " + notification)
|
|
204
|
+
|
|
205
|
+
def finish_err(self, notification: str) -> None:
|
|
206
|
+
self._log("Finished with failure: " + notification)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class LogProgressTracker(ProgressTracker):
|
|
211
|
+
def __init__(self, device: Device):
|
|
212
|
+
self.fqdn = device.fqdn
|
|
213
|
+
|
|
214
|
+
def __enter__(self) -> ProgressTracker:
|
|
215
|
+
return self
|
|
216
|
+
|
|
217
|
+
def start_group(self, group_name: str) -> None:
|
|
218
|
+
logger.info(f"{self.fqdn} - {group_name}")
|
|
219
|
+
|
|
220
|
+
def upload_files(self, files: list[str]) -> None:
|
|
221
|
+
logger.info(f"{self.fqdn} - upload %s files", len(files))
|
|
222
|
+
|
|
223
|
+
def command_done_ok(self, output: pb.CMDResult) -> None:
|
|
224
|
+
if output.error:
|
|
225
|
+
try:
|
|
226
|
+
error = output.error.decode("utf-8")
|
|
227
|
+
except UnicodeDecodeError:
|
|
228
|
+
error = str(output.error)
|
|
229
|
+
logger.warning(f"{self.fqdn} - {error}")
|
|
230
|
+
|
|
231
|
+
def command_done_error(self, error: str) -> None:
|
|
232
|
+
logger.error(f"{self.fqdn} - {error}")
|
|
233
|
+
|
|
234
|
+
def finish_ok(self, notification: str) -> None:
|
|
235
|
+
logger.info(f"{self.fqdn} - finished with success - {notification}")
|
|
236
|
+
|
|
237
|
+
def finish_err(self, notification: str) -> None:
|
|
238
|
+
logger.info(f"{self.fqdn} - finished with failure - {notification}")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class CompositeTracker(ProgressTracker):
|
|
243
|
+
def __init__(self, *trackers: ProgressTracker) -> None:
|
|
244
|
+
self.trackers = list(trackers)
|
|
245
|
+
self.stack = ExitStack()
|
|
246
|
+
|
|
247
|
+
def __enter__(self):
|
|
248
|
+
for tracker in self.trackers:
|
|
249
|
+
self.stack.enter_context(tracker)
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
253
|
+
self.stack.close()
|
|
254
|
+
|
|
255
|
+
def add_tracker(self, tracker: ProgressTracker) -> None:
|
|
256
|
+
self.trackers.append(tracker)
|
|
257
|
+
|
|
258
|
+
def start_group(self, group_name: str) -> None:
|
|
259
|
+
for tracker in self.trackers:
|
|
260
|
+
tracker.start_group(group_name)
|
|
261
|
+
|
|
262
|
+
def set_total(self, total: int) -> None:
|
|
263
|
+
for tracker in self.trackers:
|
|
264
|
+
tracker.set_total(total)
|
|
265
|
+
|
|
266
|
+
def upload_files(self, files: list[str]) -> None:
|
|
267
|
+
for tracker in self.trackers:
|
|
268
|
+
tracker.upload_files(files)
|
|
269
|
+
|
|
270
|
+
def files_uploaded(self) -> None:
|
|
271
|
+
for tracker in self.trackers:
|
|
272
|
+
tracker.files_uploaded()
|
|
273
|
+
|
|
274
|
+
def run_command(self, cmd: str) -> None:
|
|
275
|
+
for tracker in self.trackers:
|
|
276
|
+
tracker.run_command(cmd)
|
|
277
|
+
|
|
278
|
+
def run_reload_command(self, file: str, cmd: str):
|
|
279
|
+
for tracker in self.trackers:
|
|
280
|
+
tracker.run_reload_command(file, cmd)
|
|
281
|
+
|
|
282
|
+
def command_done_ok(self, output: pb.CMDResult) -> None:
|
|
283
|
+
for tracker in self.trackers:
|
|
284
|
+
tracker.command_done_ok(output)
|
|
285
|
+
|
|
286
|
+
def command_done_error(self, error: str) -> None:
|
|
287
|
+
for tracker in self.trackers:
|
|
288
|
+
tracker.command_done_error(error)
|
|
289
|
+
|
|
290
|
+
def finish_ok(self, notification: str) -> None:
|
|
291
|
+
for tracker in self.trackers:
|
|
292
|
+
tracker.finish_ok(notification)
|
|
293
|
+
|
|
294
|
+
def finish_err(self, notification: str) -> None:
|
|
295
|
+
for tracker in self.trackers:
|
|
296
|
+
tracker.finish_err(notification)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|