annet 3.10.0__py3-none-any.whl → 3.12.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.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

@@ -47,7 +47,7 @@ class NetboxAdapter(
47
47
  ],
48
48
  ):
49
49
  @abstractmethod
50
- def list_all_fqdns(self) -> list[str]:
50
+ def list_fqdns(self, query: dict[str, list[str]] | None = None) -> list[str]:
51
51
  raise NotImplementedError()
52
52
 
53
53
  @abstractmethod
@@ -37,19 +37,7 @@ class NetboxQuery(Query):
37
37
  return self.query
38
38
 
39
39
  def parse_query(self) -> Filter:
40
- query_groups = defaultdict(list)
41
- for q in self.globs:
42
- if FIELD_VALUE_SEPARATOR in q:
43
- glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2)
44
- if glob_type not in ALLOWED_GLOB_GROUPS:
45
- raise Exception(f"unknown query type: '{glob_type}'")
46
- if not param:
47
- raise Exception(f"empty param for '{glob_type}'")
48
- query_groups[glob_type].append(param)
49
- else:
50
- query_groups["name"].append(q)
51
-
52
- query_groups.default_factory = None
40
+ query_groups = parse_query(self.globs)
53
41
  return cast(Filter, query_groups)
54
42
 
55
43
  def is_empty(self) -> bool:
@@ -62,3 +50,18 @@ class NetboxQuery(Query):
62
50
  if FIELD_VALUE_SEPARATOR in q:
63
51
  return False
64
52
  return True
53
+
54
+
55
+ def parse_query(query: list[str]) -> dict[str, list[str]]:
56
+ query_groups = defaultdict(list)
57
+ for q in query:
58
+ if FIELD_VALUE_SEPARATOR in q:
59
+ glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2)
60
+ if glob_type not in ALLOWED_GLOB_GROUPS:
61
+ raise Exception(f"unknown query type: '{glob_type}'")
62
+ if not param:
63
+ raise Exception(f"empty param for '{glob_type}'")
64
+ query_groups[glob_type].append(param)
65
+ else:
66
+ query_groups["name"].append(q)
67
+ return dict(query_groups)
@@ -54,6 +54,7 @@ class BaseNetboxStorage(
54
54
  token = opts.token
55
55
  threads = opts.threads
56
56
  self.exact_host_filter = opts.exact_host_filter
57
+ self.all_hosts_filter = opts.all_hosts_filter
57
58
  self.netbox = self._init_adapter(url=url, token=token, ssl_context=ctx, threads=threads)
58
59
  self._all_fqdns: Optional[list[str]] = None
59
60
  self._id_devices: dict[int, NetboxDeviceT] = {}
@@ -87,7 +88,7 @@ class BaseNetboxStorage(
87
88
 
88
89
  def resolve_all_fdnds(self) -> list[str]:
89
90
  if self._all_fqdns is None:
90
- self._all_fqdns = self.netbox.list_all_fqdns()
91
+ self._all_fqdns = self.netbox.list_fqdns(self.all_hosts_filter)
91
92
  return self._all_fqdns
92
93
 
93
94
  def make_devices(
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from typing import Any, Optional
3
+ from .query import parse_query
3
4
 
4
5
  DEFAULT_URL = "http://localhost"
5
6
 
@@ -12,17 +13,24 @@ class NetboxStorageOpts:
12
13
  insecure: bool = False,
13
14
  exact_host_filter: bool = False,
14
15
  threads: int = 1,
16
+ all_hosts_filter: dict[str, list[str]] | None = None,
15
17
  ):
16
18
  self.url = url
17
19
  self.token = token
18
20
  self.insecure = insecure
19
21
  self.exact_host_filter = exact_host_filter
20
22
  self.threads = threads
23
+ self.all_hosts_filter: dict[str, list[str]] = all_hosts_filter or {}
21
24
 
22
25
  @classmethod
23
26
  def parse_params(cls, conf_params: Optional[dict[str, str]], cli_opts: Any):
24
27
  url = os.getenv("NETBOX_URL") or conf_params.get("url") or DEFAULT_URL
25
28
  token = os.getenv("NETBOX_TOKEN", "").strip() or conf_params.get("token") or ""
29
+ all_hosts_filter = None
30
+ if all_hosts_filter_env := os.getenv("NETBOX_ALL_HOSTS_FILTER", "").strip():
31
+ all_hosts_filter = parse_query(all_hosts_filter_env.split(","))
32
+ elif all_hosts_filter_params := conf_params.get("all_hosts_filter"):
33
+ all_hosts_filter = all_hosts_filter_params
26
34
  threads = os.getenv("NETBOX_CLIENT_THREADS", "").strip() or conf_params.get("threads") or "1"
27
35
  insecure = False
28
36
  if insecure_env := os.getenv("NETBOX_INSECURE", "").lower():
@@ -39,4 +47,5 @@ class NetboxStorageOpts:
39
47
  insecure=insecure,
40
48
  exact_host_filter=exact_host_filter,
41
49
  threads=int(threads),
50
+ all_hosts_filter=all_hosts_filter,
42
51
  )
@@ -70,10 +70,11 @@ class NetboxV37Adapter(NetboxAdapter[
70
70
  list[FHRPGroupV37],
71
71
  )
72
72
 
73
- def list_all_fqdns(self) -> list[str]:
73
+ def list_fqdns(self, query: dict[str, list[str]] | None = None) -> list[str]:
74
+ query = query or {}
74
75
  return [
75
76
  d.name
76
- for d in self.netbox.dcim_all_devices_brief().results
77
+ for d in self.netbox.dcim_all_devices_brief(**query).results
77
78
  ]
78
79
 
79
80
  def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceV37]:
@@ -71,10 +71,11 @@ class NetboxV41Adapter(NetboxAdapter[
71
71
  list[FHRPGroupV41],
72
72
  )
73
73
 
74
- def list_all_fqdns(self) -> list[str]:
74
+ def list_fqdns(self, query: dict[str, list[str]] | None = None) -> list[str]:
75
+ query = query or {}
75
76
  return [
76
77
  d.name
77
- for d in self.netbox.dcim_all_devices_brief().results
78
+ for d in self.netbox.dcim_all_devices_brief(**query).results
78
79
  ]
79
80
 
80
81
  def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceV41]:
@@ -72,10 +72,11 @@ class NetboxV42Adapter(NetboxAdapter[
72
72
  list[FHRPGroupV41],
73
73
  )
74
74
 
75
- def list_all_fqdns(self) -> list[str]:
75
+ def list_fqdns(self, query: dict[str, list[str]] | None = None) -> list[str]:
76
+ query = query or {}
76
77
  return [
77
78
  d.name
78
- for d in self.netbox.dcim_all_devices_brief().results
79
+ for d in self.netbox.dcim_all_devices_brief(**query).results
79
80
  ]
80
81
 
81
82
  def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceV42]:
@@ -1,10 +1,11 @@
1
1
  import functools
2
2
  import json
3
3
  import re
4
- from os import path
4
+ from pathlib import Path
5
5
  from typing import Any, Dict
6
6
 
7
7
  from annet.annlib.netdev.db import find_true_sequences, get_db
8
+ from annet.lib import get_context
8
9
 
9
10
 
10
11
  @functools.lru_cache(None)
@@ -23,6 +24,7 @@ def _prepare_db() -> Dict[str, Any]:
23
24
  from library.python import resource
24
25
  raw = json.loads(resource.resfs_read("contrib/python/annet/annet/annlib/netdev/devdb/data/devdb.json").decode("utf-8"))
25
26
  except ImportError:
26
- with open(path.join(path.dirname(__file__), "data", "devdb.json"), "r") as f:
27
+ devdb_file = Path(get_context().get("devdb", {}).get("path", Path(__file__).parent / "data" / "devdb.json"))
28
+ with devdb_file.open("r", encoding="utf-8") as f:
27
29
  raw = json.load(f)
28
30
  return {tuple(seq.split(".")): re.compile(regexp) for (seq, regexp) in raw.items()}
annet/api/__init__.py CHANGED
@@ -331,7 +331,15 @@ def diff(
331
331
  loader: ann_gen.Loader,
332
332
  device_ids: List[Any]
333
333
  ) -> tuple[Mapping[Device, Union[Diff, PCDiff]], Mapping[Device, Exception]]:
334
- """ Сгенерировать конфиг для устройств """
334
+ """ Сгенерировать дифф для устройств """
335
+ if args.config == "running":
336
+ fetcher = annet.deploy.get_fetcher()
337
+ ann_gen.live_configs = annet.lib.do_async(
338
+ fetcher.fetch(
339
+ [device for device in loader.devices if device.id in device_ids],
340
+ processes=args.parallel
341
+ )
342
+ )
335
343
  stdin = args.stdin(filter_acl=args.filter_acl, config=None)
336
344
 
337
345
  filterer = filtering.filterer_connector.get()
@@ -1,7 +1,7 @@
1
1
  # pylint: disable=unused-argument
2
2
 
3
3
  from annet.annlib.types import Op
4
- from annet.executor import CommandList, Command
4
+ from annet.annlib.command import Command, CommandList
5
5
 
6
6
 
7
7
  def apply(hw, do_commit, do_finalize, **_):
@@ -200,9 +200,11 @@ aaa
200
200
 
201
201
  hwtacacs-server template *
202
202
  hwtacacs-server shared-key
203
+ hwtacacs-server source-ip
203
204
 
204
205
  hwtacacs server template *
205
206
  hwtacacs server shared-key
207
+ hwtacacs server source-ip
206
208
 
207
209
  !radius-server template default
208
210
 
@@ -64,8 +64,36 @@ class FormatterContext:
64
64
  return self.next and self.next[0]
65
65
 
66
66
 
67
+ class NotUniquePatch:
68
+ def __init__(self):
69
+ """In the case of comments, odict is not suitable: there may be several identical edit and exit"""
70
+ self._items = []
71
+ self._keys = set()
72
+
73
+ def __setitem__(self, key, value):
74
+ self._keys.add(key)
75
+ self._items.append((key, value))
76
+
77
+ def keys(self):
78
+ return list(self)
79
+
80
+ def items(self):
81
+ return self._items
82
+
83
+ def __contains__(self, item):
84
+ return item in self._keys
85
+
86
+ def __iter__(self):
87
+ return iter(item[0] for item in self._items)
88
+
89
+ def __bool__(self) -> bool:
90
+ return bool(self._items)
91
+
92
+
67
93
  # =====
68
94
  class CommonFormatter:
95
+ cmd_path_cls = odict
96
+
69
97
  def __init__(self, indent=" "):
70
98
  self._indent = indent
71
99
  self._block_begin = ""
@@ -96,7 +124,7 @@ class CommonFormatter:
96
124
  )
97
125
 
98
126
  def cmd_paths(self, patch: "PatchTree") -> odict:
99
- ret = odict()
127
+ ret = self.cmd_path_cls()
100
128
  path = []
101
129
  for row, context in self.blocks_and_context(patch, is_patch=True):
102
130
  if row is BlockBegin:
@@ -222,7 +250,20 @@ class BlockExitFormatter(CommonFormatter):
222
250
  yield exit_statement, last_row_context
223
251
 
224
252
 
253
+ class HuaweiPatch(NotUniquePatch):
254
+ policy_end_blocks = ("end-list", "endif", "end-filter")
255
+
256
+ def __setitem__(self, key, value):
257
+ if not key:
258
+ return
259
+
260
+ if key not in self or (key[0].startswith("xpl") and key[-1] in self.policy_end_blocks):
261
+ super().__setitem__(key, value)
262
+
263
+
225
264
  class HuaweiFormatter(BlockExitFormatter):
265
+ cmd_path_cls = HuaweiPatch
266
+
226
267
  def __init__(self, indent=" "):
227
268
  super().__init__(
228
269
  block_exit="quit",
@@ -237,9 +278,8 @@ class HuaweiFormatter(BlockExitFormatter):
237
278
  def split(self, text):
238
279
  # на старых прошивка наблюдается баг с двумя пробелами в этом месте в конфиге
239
280
  # например на VRP V100R006C00SPC500 + V100R006SPH003
240
- policy_end_blocks = ("end-list", "endif", "end-filter")
241
281
  tree = self.split_remove_spaces(text)
242
- tree[:] = filter(lambda x: not str(x).strip().startswith(policy_end_blocks), tree)
282
+ tree[:] = filter(lambda x: not str(x).strip().startswith(HuaweiPatch.policy_end_blocks), tree)
243
283
  return tree
244
284
 
245
285
  def block_exit(self, context: Optional[FormatterContext]):
@@ -256,7 +296,9 @@ class HuaweiFormatter(BlockExitFormatter):
256
296
  return
257
297
 
258
298
  if parent_row.startswith("xpl route-filter"):
259
- if (row.startswith(("if", "elseif")) and row.endswith("then")) and not row_next:
299
+ if (row.startswith(("if", "elseif")) and row.endswith("then")) and (
300
+ not row_next or not row_next.startswith(("elseif", "else"))
301
+ ):
260
302
  yield "endif"
261
303
  elif row == "else":
262
304
  yield "endif"
@@ -407,29 +449,9 @@ class AsrFormatter(BlockExitFormatter):
407
449
  yield from super().block_exit(context)
408
450
 
409
451
 
410
- class JuniperPatch:
411
- def __init__(self):
412
- """In the case of comments, odict is not suitable: there may be several identical edit and exit"""
413
- self._items = []
414
-
415
- def __setitem__(self, key, value):
416
- self._items.append((key, value))
417
-
418
- def keys(self):
419
- return list(self)
420
-
421
- def items(self):
422
- return self._items
423
-
424
- def __iter__(self):
425
- return iter(item[0] for item in self._items)
426
-
427
- def __bool__(self) -> bool:
428
- return bool(self._items)
429
-
430
-
431
452
  class JuniperFormatter(CommonFormatter):
432
453
  patch_set_prefix = "set"
454
+ cmd_path_cls = NotUniquePatch
433
455
 
434
456
  @dataclasses.dataclass
435
457
  class Comment:
@@ -526,7 +548,7 @@ class JuniperFormatter(CommonFormatter):
526
548
  yield line + self._statement_end
527
549
 
528
550
  def cmd_paths(self, patch, _prev=tuple()):
529
- commands = JuniperPatch()
551
+ commands = self.cmd_path_cls()
530
552
 
531
553
  for item in patch.itms:
532
554
  key, childs, context = item.row, item.child, item.context
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: annet
3
- Version: 3.10.0
3
+ Version: 3.12.0
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -8,7 +8,6 @@ annet/connectors.py,sha256=aoiDVLPizx8CW2p8SAwGCzyO_WW8H9xc2aujbGC4bDg,4882
8
8
  annet/deploy.py,sha256=ZTZcomYm2KJkKMyG6XzZaAMPr-Tduwcv67BhsoLARkY,7673
9
9
  annet/deploy_ui.py,sha256=XsN1i7E9cNp1SAf1YBwhEBuwN91MvlNMSrLhnQrumA8,28672
10
10
  annet/diff.py,sha256=kD_2kxz5wc2TP10xj-BHs6IPq1yNKkXxIco8czjeC6M,9497
11
- annet/executor.py,sha256=INlWAZFLpHurg8GTXclbSzaeSIXgZo4ccmcRulQqr88,5130
12
11
  annet/filtering.py,sha256=ZtqxPsKdV9reZoRxtQyBg22BqyMqd-2SotYcxZ-68AQ,903
13
12
  annet/gen.py,sha256=y9e7P551uJ-thjBS2p-ZRMgwQa8kPF_JAoEXJ3am77Y,30683
14
13
  annet/hardware.py,sha256=O2uadehcavZ10ssPr-db3XYHK8cpbG7C7XFkO-I6r_s,1161
@@ -31,26 +30,26 @@ annet/adapters/file/provider.py,sha256=3hNt0QQg46SVymLQ4Bh9G4VNYyhnB7gV5yu5OiIJp
31
30
  annet/adapters/netbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
31
  annet/adapters/netbox/provider.py,sha256=K6P5I2UjGZ3N3ayoUaVw10mf9lE5SD8W0kpB7TSefNA,2272
33
32
  annet/adapters/netbox/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- annet/adapters/netbox/common/adapter.py,sha256=L9xFMcYk7E7w6MH8N0qdg8h-CSP4nZzrOMFlw0_33EU,2600
33
+ annet/adapters/netbox/common/adapter.py,sha256=1rR9PZAU72hIreozWFVb9CKpFVjfUH_i9kQEbaNiEUI,2639
35
34
  annet/adapters/netbox/common/client.py,sha256=PaxHG4W9H8_uunIwMBNYkLq4eQJYoO6p6gY-ciQs7Nc,2563
36
35
  annet/adapters/netbox/common/manufacturer.py,sha256=9jTfzwx5XmETrjSbIJu_FhNaByaUbGQE787c5rBor-4,1137
37
36
  annet/adapters/netbox/common/models.py,sha256=wm0aIxsv7nvQfCXoqI5sWSxRW3guLCB_aQU7KDiB0YY,8925
38
- annet/adapters/netbox/common/query.py,sha256=kbNQSZwkjFeDArHwA8INHUauxCxYElXtNh58pZipWdo,1867
37
+ annet/adapters/netbox/common/query.py,sha256=0GeIiW_ttnESaNKHIziF61P-3DX0XD592UHQGn_1m60,1911
39
38
  annet/adapters/netbox/common/status_client.py,sha256=POaqiQJ0jPcqUQH-X_fWHVnKB7TBYveNriaT0eNTlfI,769
40
- annet/adapters/netbox/common/storage_base.py,sha256=GVF30gV4X828MPhUOZ3HsNhWvYp0J5k6LoRFd2UOk7c,9712
41
- annet/adapters/netbox/common/storage_opts.py,sha256=wfv1spElomwgVYMCgGth3SWVF0RsRgtUgq9GpFL9hJs,1520
39
+ annet/adapters/netbox/common/storage_base.py,sha256=lMRiDkzQylpA5Fxa4B6CUOWzq2d1cpjeSDbFGp8jH7o,9787
40
+ annet/adapters/netbox/common/storage_opts.py,sha256=8LCxX0m-Wd-jNZ2iRAzkBJnbDg-MuE7RS17uPxjZo3o,2066
42
41
  annet/adapters/netbox/v24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
42
  annet/adapters/netbox/v24/models.py,sha256=RH2ooUPHOtHT0q1ZQE7v23FgmcsGenzzSgJyft13o1k,7605
44
43
  annet/adapters/netbox/v24/storage.py,sha256=zptzrW4aZUv_exuGw0DqQPm_kXZR3DwL4zRHYL6twTk,6219
45
44
  annet/adapters/netbox/v37/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
45
  annet/adapters/netbox/v37/models.py,sha256=LTjotRcTYWUR3KFvdg3LjBhek4OQblOvJr5yvP9AAPg,1940
47
- annet/adapters/netbox/v37/storage.py,sha256=XBYuDvNWngofXf7Kiy-V-AyGXfIszP8UQyf1N0Obq5c,5041
46
+ annet/adapters/netbox/v37/storage.py,sha256=1CO1s1lM01vjN5o6MOF-WK7nHsY8cSJg5KrAj4Z-N8Y,5115
48
47
  annet/adapters/netbox/v41/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
48
  annet/adapters/netbox/v41/models.py,sha256=RI8QHpNIcrXNubis1rUFJ1j9bL881fz4meLXvAY8Yi0,2197
50
- annet/adapters/netbox/v41/storage.py,sha256=kK_oyWNa_Ha2vSk66ca4Uxq9OZsep3y2XsRWrLp_L-Q,5095
49
+ annet/adapters/netbox/v41/storage.py,sha256=FDdaUugvFEMACSRLW8T8HRv_1V7VbTN95u5wvIgt4Mo,5169
51
50
  annet/adapters/netbox/v42/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
51
  annet/adapters/netbox/v42/models.py,sha256=JNrBMeZ1SznjRzaqKNBP5jtHEKHN40X1V-BlgrMpKwE,2054
53
- annet/adapters/netbox/v42/storage.py,sha256=zScnvOqjPuS39vwXcE-QDkCUosBr-nafDdgFA4jzioY,6022
52
+ annet/adapters/netbox/v42/storage.py,sha256=mXswX_wqXIcRFKAyTbjYkPR2IIm9_SNeGJDBw9-ZIl8,6096
54
53
  annet/annlib/__init__.py,sha256=fT1l4xV5fqqg8HPw9HqmZVN2qwS8i6X1aIm2zGDjxKY,252
55
54
  annet/annlib/command.py,sha256=GsgzQ8oFiHFan1Tfyo340rWgfhyU4_sCKJGhBm2jG6Y,1258
56
55
  annet/annlib/diff.py,sha256=MZ6eQAU3cadQp8KaSE6uAYFtcfMDCIe_eNuVROnYkCk,4496
@@ -63,7 +62,7 @@ annet/annlib/patching.py,sha256=1bxlLDRx15GjB6aNk6kAx7t3WPPLHffYvSy7VLGnCig,2150
63
62
  annet/annlib/types.py,sha256=VHU0CBADfYmO0xzB_c5f-mcuU3dUumuJczQnqGlib9M,852
64
63
  annet/annlib/netdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
64
  annet/annlib/netdev/db.py,sha256=fI_u5aya4l61mbYSjj4JwlVfi3s7obt2jqERSuXGRUI,1634
66
- annet/annlib/netdev/devdb/__init__.py,sha256=aKYjjLbJebdKBjnGDpVLQdSqrV2JL24spGm58tmMWVU,892
65
+ annet/annlib/netdev/devdb/__init__.py,sha256=I-NKzenyjsmUKpmIerQOfZExnnnDpPdNZLdRanyu-Nk,1020
67
66
  annet/annlib/netdev/devdb/data/devdb.json,sha256=HI43va6tlOAFRSRnjXR7Ex32B4ZbRH0yvrjzud2apJM,7198
68
67
  annet/annlib/netdev/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
68
  annet/annlib/netdev/views/dump.py,sha256=rIlyvnA3uM8bB_7oq1nS2KDxTp6dQh2hz-FbNhYIpOU,4630
@@ -76,7 +75,7 @@ annet/annlib/rbparser/platform.py,sha256=d1jFH8hGMOf_qiveKE4H-c0OfNnzwmt2VYpSiSO
76
75
  annet/annlib/rbparser/syntax.py,sha256=PlKyWqS3zwPwumILmKIci-DmEAbfZp8YycpW1mNqADM,3602
77
76
  annet/annlib/rulebook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
77
  annet/annlib/rulebook/common.py,sha256=6A3sOkOanDRTuR7F426zakShGaGKRBmBLDCnqBbBJr8,13531
79
- annet/api/__init__.py,sha256=-VLQ-Uf2WSdNHXirzdMjgaY8UzrXbGpPHmUnwzB_5YI,33763
78
+ annet/api/__init__.py,sha256=LqCBT_-yL-FmDrF80fzm_qEZSCRbiiiyiivExOCgALo,34061
80
79
  annet/configs/context.yml,sha256=RVLrKLIHpCty7AGwOnmqf7Uu0iZQCn-AjYhophDJer8,259
81
80
  annet/configs/logging.yaml,sha256=EUagfir99QqA73Scc3k7sfQccbU3E1SvEQdyhLFtCl4,997
82
81
  annet/generators/__init__.py,sha256=LnW6G_7K5ScGYkFjFk7_QfuUaFAk5oTSHYaTaL6xUwA,16735
@@ -124,7 +123,7 @@ annet/rulebook/arista/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
124
123
  annet/rulebook/arista/aaa.py,sha256=7fBTwBnz9SOqYbwG8GBeEQTJG0e4uC4HkuZJeMG-kAY,2250
125
124
  annet/rulebook/arista/iface.py,sha256=bgj6a3r__-OE6VyYbSfnD6ps2QJKyX028W7IFJww-UY,720
126
125
  annet/rulebook/aruba/__init__.py,sha256=ILggeID6kNWcDBGzhXm_mebcfkuLSq5prBFU5DyPFs4,500
127
- annet/rulebook/aruba/ap_env.py,sha256=5fUVLhXH4-0DAtv8t0yoqJUibaRMuzF8Q7mGFzNsEN8,4692
126
+ annet/rulebook/aruba/ap_env.py,sha256=jkiDv-tYd6nsOdQPGTrh5XJIZQBNdVNVB25Tw-UbTnc,4698
128
127
  annet/rulebook/aruba/misc.py,sha256=O0p_wsR07gtB8rm1eJvJ7VYnGm5T8Uau_SVKR9FVInI,234
129
128
  annet/rulebook/b4com/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
129
  annet/rulebook/b4com/file.py,sha256=zK7RwBk1YaVoDSFSg1u7Pt8u0Fk3nhhu27aJRngemwc,137
@@ -161,7 +160,7 @@ annet/rulebook/texts/cisco.order,sha256=DarNICBBAEXR8VOFV8SNnuQmwGAG-TGwXlMWpFMT
161
160
  annet/rulebook/texts/cisco.rul,sha256=cX9CyqKpgpr8B1Qe3aEe66wZpfaGg7fmsIsmgW6Yip4,3619
162
161
  annet/rulebook/texts/huawei.deploy,sha256=5vrvhai2BjaJ793A3EHHCthic3dnGKp2_rAgViVIouI,10951
163
162
  annet/rulebook/texts/huawei.order,sha256=4qXuoqfJ8Hck11LACKwaWP2ARHwPrtLfCut1BDRAg7E,11244
164
- annet/rulebook/texts/huawei.rul,sha256=-Da3qjDaAWeW1IFMsLmAzo8XTCkqc2PGTqPGV8v3btA,13705
163
+ annet/rulebook/texts/huawei.rul,sha256=xVGFGOidwnQPaMbXhQCRuk-yUEMVTl5A9_TsOxQbjNY,13765
165
164
  annet/rulebook/texts/iosxr.deploy,sha256=Hu0NkcGv3f1CWUrnbzI3eQOPXJxtH4NNOPRV68IrW4U,1226
166
165
  annet/rulebook/texts/iosxr.order,sha256=gUp6XHwzqkDsArCUAwtx3rR1qlGfYsHy2vP9oZN2oDk,1922
167
166
  annet/rulebook/texts/iosxr.rul,sha256=JMFJ94ORNeDQo_X73iPS6pFUmXYTBuL5pkUypgHcOig,2966
@@ -184,7 +183,7 @@ annet/rulebook/texts/routeros.rul,sha256=ipfxjj0mjFef6IsUlupqx4BY_Je_OUb8u_U1019
184
183
  annet/vendors/__init__.py,sha256=gQcDFlKeWDZB6vxJ_MdPWEoE-C5dg-YgXvgGkuV9YLw,569
185
184
  annet/vendors/base.py,sha256=AmM3--gqC-Rpw5Xu_-hqthWZ9EoZRL8x6eOHwadZGbo,1145
186
185
  annet/vendors/registry.py,sha256=LgPg4oxWrgxsfpLpJ6OWEGFmUzlVlHzziSXsZTi82uc,2540
187
- annet/vendors/tabparser.py,sha256=EQrJb0Tz0Lj5byJ8Qr5fayYUCgKDgpJcAz4Smb2kxvs,32374
186
+ annet/vendors/tabparser.py,sha256=BXPYppgJf4OaobFrlpVr7APzG-NY6e0wwgGdp6YsVdU,32953
188
187
  annet/vendors/library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
188
  annet/vendors/library/arista.py,sha256=J4ltZ7sS_TgIECg2U7fizvxwfS4-s35Of0tNDNWWYbE,1302
190
189
  annet/vendors/library/aruba.py,sha256=tvrSFSA43n0uelCv-NLQnqxO01d0y2mrfhncpOX7zoQ,1257
@@ -200,8 +199,8 @@ annet/vendors/library/optixtrans.py,sha256=VdME69Ca4HAEgoaKN21fZxnmmsqqaxOe_HZja
200
199
  annet/vendors/library/pc.py,sha256=vfv31_NPi7M-4AUDL89UcpawK2E6xvCpELA209cd1ho,1086
201
200
  annet/vendors/library/ribbon.py,sha256=DDOBq-_-FL9dCxqXs2inEWZ-pvw-dJ-A-prA7cKMhec,1216
202
201
  annet/vendors/library/routeros.py,sha256=iQa7m_4wjuvcgBOI9gyZwlw1BvzJfOkvUbyoEk-NI9I,1254
203
- annet-3.10.0.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
204
- annet-3.10.0.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
202
+ annet-3.12.0.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
203
+ annet-3.12.0.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
205
204
  annet_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
206
205
  annet_generators/example/__init__.py,sha256=OJ77uj8axc-FIyIu_Xdcnzmde3oQW5mk5qbODkhuVc8,355
207
206
  annet_generators/example/hostname.py,sha256=RloLzNVetEoWPLITzfJ13Nk3CC0yi-cZB1RTd6dnuhI,2541
@@ -214,8 +213,8 @@ annet_generators/rpl_example/generator.py,sha256=EWah19gOH8G-QyNyWqxCqdRi0BK7GbM
214
213
  annet_generators/rpl_example/items.py,sha256=HPgxScDvSqJPdz0c2SppDrH82DZYC4zUaniQwcWmh4A,1176
215
214
  annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
216
215
  annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
217
- annet-3.10.0.dist-info/METADATA,sha256=vNT4cNxluWuvug1t-yfWrqYFXmoijXsZNzXM2UUysUk,816
218
- annet-3.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
219
- annet-3.10.0.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
220
- annet-3.10.0.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
221
- annet-3.10.0.dist-info/RECORD,,
216
+ annet-3.12.0.dist-info/METADATA,sha256=XJu0SgF3B3HRMka4Qd0fPAZKXPWvHQVdx7FYepCMpqQ,816
217
+ annet-3.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
218
+ annet-3.12.0.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
219
+ annet-3.12.0.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
220
+ annet-3.12.0.dist-info/RECORD,,
annet/executor.py DELETED
@@ -1,172 +0,0 @@
1
- import asyncio
2
- import os
3
- import statistics
4
- from abc import ABC, abstractmethod
5
- from functools import partial
6
- from operator import itemgetter
7
- from typing import Any, List, Optional
8
-
9
- import colorama
10
- from annet.annlib.command import Command, CommandList, Question # noqa: F401
11
-
12
-
13
- class CommandResult(ABC):
14
- @abstractmethod
15
- def get_out(self) -> str:
16
- pass
17
-
18
-
19
- class ExecutorException(Exception):
20
- def __init__(self, *args: List[Any], auxiliary: Optional[Any] = None, **kwargs: object):
21
- self.auxiliary = auxiliary
22
- super().__init__(*args, **kwargs)
23
-
24
- def __repr__(self) -> str:
25
- return "%s(args=%r,auxiliary=%s)" % (self.__class__.__name__, self.args, self.auxiliary)
26
-
27
-
28
- class ExecException(ExecutorException):
29
- def __init__(self, msg: str, cmd: str, res: str, **kwargs):
30
- super().__init__(**kwargs)
31
- self.args = msg, cmd, res
32
- self.kwargs = kwargs
33
- self.msg = msg
34
- self.cmd = cmd
35
- self.res = res
36
-
37
- def __str__(self) -> str:
38
- return str(self.msg)
39
-
40
- def __repr__(self) -> str:
41
- return "%s<%s, %s>" % (self.__class__.__name__, self.msg, self.cmd)
42
-
43
-
44
- class BadCommand(ExecException):
45
- pass
46
-
47
-
48
- class NonzeroRetcode(ExecException):
49
- pass
50
-
51
-
52
- class CommitException(ExecException):
53
- pass
54
-
55
-
56
- def _show_type_summary(caption, items, total, stat_items=None):
57
- if items:
58
- if not stat_items:
59
- stat = ""
60
- else:
61
- avg = statistics.mean(stat_items)
62
- stat = " %(min).1f/%(max).1f/%(avg).1f/%(stdev)s (min/max/avg/stdev)" % dict(
63
- min=min(stat_items),
64
- max=max(stat_items),
65
- avg=avg,
66
- stdev="-" if len(stat_items) < 2 else "%.1f" % statistics.stdev(stat_items, xbar=avg)
67
- )
68
-
69
- print("%-8s %d of %d%s" % (caption, len(items), total, stat))
70
-
71
-
72
- def show_bulk_report(hostnames, res, durations, log_dir):
73
- total = len(hostnames)
74
- if not total:
75
- return
76
-
77
- colorama.init()
78
-
79
- print("\n====== bulk deploy report ======")
80
-
81
- done = [host for (host, hres) in res.items() if not isinstance(hres, Exception)]
82
- cancelled = [host for (host, hres) in res.items() if isinstance(hres, asyncio.CancelledError)]
83
- failed = [host for (host, hres) in res.items() if isinstance(hres, Exception) and host not in cancelled]
84
- lost = [host for host in hostnames if host not in res]
85
- limit = 30
86
-
87
- _show_type_summary("Done :", done, total, [durations[h] for h in done])
88
- _print_limit(done, partial(_print_hostname, style=colorama.Fore.GREEN), limit, total)
89
-
90
- _show_type_summary("Failed :", failed, total, [durations[h] for h in failed])
91
-
92
- _print_limit(failed, partial(_print_failed, res=res), limit, total)
93
-
94
- _show_type_summary("Cancelled :", cancelled, total, [durations[h] for h in cancelled if durations[h] is not None])
95
- _print_limit(cancelled, partial(_print_hostname, style=colorama.Fore.RED), limit, total)
96
-
97
- _show_type_summary("Lost :", lost, total)
98
- _print_limit(lost, _print_hostname, limit, total)
99
-
100
- err_limit = 5
101
- if failed:
102
- errs = {}
103
- for hostname in failed:
104
- fmt_err = _format_exc(res[hostname])
105
- if fmt_err in errs:
106
- errs[fmt_err] += 1
107
- else:
108
- errs[fmt_err] = 1
109
- print("Top errors :")
110
- for fmt_err, n in sorted(errs.items(), key=itemgetter(1), reverse=True)[:err_limit]:
111
- print(" %-4d %s" % (n, fmt_err))
112
- print("\n", end="")
113
-
114
- if log_dir:
115
- print("See deploy logs in %s/\n" % os.path.relpath(log_dir))
116
-
117
-
118
- def _format_exc(exc):
119
- if isinstance(exc, ExecException):
120
- cmd = str(exc.cmd)
121
- if len(cmd) > 50:
122
- cmd = cmd[:50] + "~.."
123
- return "'%s', cmd '%s'" % (exc.msg, cmd)
124
- elif isinstance(exc, ExecutorException):
125
- return "%s%r" % (exc.__class__.__name__, exc.args) # исключить многословный auxiliary
126
- else:
127
- return repr(exc)
128
-
129
-
130
- def _print_hostname(host, style=None):
131
- if style:
132
- host = style + host + colorama.Style.RESET_ALL
133
- print(" %s" % host)
134
-
135
-
136
- def _print_limit(items, printer, limit, total, end="\n"):
137
- if not items:
138
- return
139
- if len(items) > limit and len(items) > total * 0.7:
140
- print(" ... %d hosts" % len(items))
141
- for host in items[:limit]:
142
- printer(host)
143
- if len(items) > limit:
144
- print(" ... %d more hosts" % (len(items) - limit))
145
-
146
- print(end, end="")
147
-
148
-
149
- def _print_failed(host, res):
150
- exc = res[host]
151
- color = colorama.Fore.YELLOW if isinstance(exc, Warning) else colorama.Fore.RED
152
- print(" %s - %s" % (color + host + colorama.Style.RESET_ALL, _format_exc(exc)))
153
-
154
-
155
- class DeferredFileWrite:
156
- def __init__(self, file, mode="r"):
157
- self._file = file
158
- wrapper = {"w": "a", "wb": "ab"}
159
- if mode in wrapper:
160
- self._mode = wrapper[mode]
161
- else:
162
- raise Exception()
163
-
164
- def write(self, data):
165
- with open(self._file, self._mode, encoding="utf-8") as fh:
166
- fh.write(data)
167
-
168
- def close(self):
169
- pass
170
-
171
- def flush(self):
172
- pass
File without changes