annet 0.5__py3-none-any.whl → 0.7__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.

Files changed (40) hide show
  1. annet/adapters/__init__.py +0 -0
  2. annet/adapters/netbox/__init__.py +0 -0
  3. annet/adapters/netbox/common/__init__.py +0 -0
  4. annet/adapters/netbox/common/client.py +87 -0
  5. annet/adapters/netbox/common/manufacturer.py +62 -0
  6. annet/adapters/netbox/common/models.py +98 -0
  7. annet/adapters/netbox/common/query.py +23 -0
  8. annet/adapters/netbox/common/status_client.py +24 -0
  9. annet/adapters/netbox/common/storage_opts.py +14 -0
  10. annet/adapters/netbox/provider.py +34 -0
  11. annet/adapters/netbox/v24/__init__.py +0 -0
  12. annet/adapters/netbox/v24/api_models.py +72 -0
  13. annet/adapters/netbox/v24/client.py +59 -0
  14. annet/adapters/netbox/v24/storage.py +190 -0
  15. annet/adapters/netbox/v37/__init__.py +0 -0
  16. annet/adapters/netbox/v37/api_models.py +37 -0
  17. annet/adapters/netbox/v37/client.py +62 -0
  18. annet/adapters/netbox/v37/storage.py +143 -0
  19. annet/annlib/filter_acl.py +1 -1
  20. annet/api/__init__.py +27 -16
  21. annet/cli.py +6 -2
  22. annet/cli_args.py +10 -0
  23. annet/deploy.py +96 -1
  24. annet/diff.py +1 -2
  25. annet/gen.py +7 -3
  26. annet/generators/__init__.py +7 -9
  27. annet/output.py +3 -1
  28. annet/rulebook/aruba/ap_env.py +3 -3
  29. annet/rulebook/texts/huawei.rul +2 -2
  30. {annet-0.5.dist-info → annet-0.7.dist-info}/METADATA +3 -1
  31. {annet-0.5.dist-info → annet-0.7.dist-info}/RECORD +37 -21
  32. {annet-0.5.dist-info → annet-0.7.dist-info}/WHEEL +1 -1
  33. annet-0.7.dist-info/entry_points.txt +5 -0
  34. {annet-0.5.dist-info → annet-0.7.dist-info}/top_level.txt +0 -1
  35. annet_generators/example/lldp.py +1 -0
  36. annet-0.5.dist-info/entry_points.txt +0 -6
  37. annet_nbexport/__init__.py +0 -220
  38. annet_nbexport/main.py +0 -46
  39. {annet-0.5.dist-info → annet-0.7.dist-info}/AUTHORS +0 -0
  40. {annet-0.5.dist-info → annet-0.7.dist-info}/LICENSE +0 -0
@@ -0,0 +1,37 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import List, Optional, Any
4
+
5
+ from annet.adapters.netbox.common.models import (
6
+ Entity, Label, DeviceType, DeviceIp,
7
+ )
8
+
9
+
10
+ @dataclass
11
+ class Interface(Entity):
12
+ device: Entity
13
+ enabled: bool
14
+ display: str = "" # added in 3.x
15
+
16
+
17
+ @dataclass
18
+ class Device(Entity):
19
+ display: str # renamed in 3.x from display_name
20
+ device_type: DeviceType
21
+ device_role: Entity
22
+ tenant: Optional[Entity]
23
+ platform: Optional[Entity]
24
+ serial: str
25
+ asset_tag: Optional[str]
26
+ site: Entity
27
+ rack: Optional[Entity]
28
+ position: Optional[float]
29
+ face: Optional[Label]
30
+ status: Label
31
+ primary_ip: Optional[DeviceIp]
32
+ primary_ip4: Optional[DeviceIp]
33
+ primary_ip6: Optional[DeviceIp]
34
+ tags: List[Entity]
35
+ custom_fields: dict[str, Any]
36
+ created: datetime
37
+ last_updated: datetime
@@ -0,0 +1,62 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ import dateutil.parser
5
+ from adaptix import Retort, loader
6
+ from dataclass_rest import get
7
+ from dataclass_rest.client_protocol import FactoryProtocol
8
+
9
+ from annet.adapters.netbox.common.client import (
10
+ BaseNetboxClient, collect, PagingResponse,
11
+ )
12
+ from annet.adapters.netbox.common.models import IpAddress
13
+ from .api_models import Device, Interface
14
+
15
+
16
+ class NetboxV37(BaseNetboxClient):
17
+ def _init_response_body_factory(self) -> FactoryProtocol:
18
+ return Retort(recipe=[
19
+ loader(datetime, dateutil.parser.parse)
20
+ ])
21
+
22
+ @get("dcim/interfaces")
23
+ def interfaces(
24
+ self,
25
+ device_id: Optional[List[int]] = None,
26
+ limit: int = 20,
27
+ offset: int = 0,
28
+ ) -> PagingResponse[Interface]:
29
+ pass
30
+
31
+ all_interfaces = collect(interfaces, field="device_id")
32
+
33
+ @get("ipam/ip-addresses")
34
+ def ip_addresses(
35
+ self,
36
+ interface_id: Optional[List[int]] = None,
37
+ limit: int = 20,
38
+ offset: int = 0,
39
+ ) -> PagingResponse[IpAddress]:
40
+ pass
41
+
42
+ all_ip_addresses = collect(ip_addresses, field="interface_id")
43
+
44
+ @get("dcim/devices")
45
+ def devices(
46
+ self,
47
+ name: Optional[List[str]] = None,
48
+ name__ic: Optional[List[str]] = None,
49
+ tag: Optional[List[str]] = None,
50
+ limit: int = 20,
51
+ offset: int = 0,
52
+ ) -> PagingResponse[Device]:
53
+ pass
54
+
55
+ all_devices = collect(devices)
56
+
57
+ @get("dcim/devices/{device_id}")
58
+ def get_device(
59
+ self,
60
+ device_id: int,
61
+ ) -> Device:
62
+ pass
@@ -0,0 +1,143 @@
1
+ from logging import getLogger
2
+ from typing import Optional, List
3
+
4
+ from adaptix import P
5
+ from adaptix.conversion import impl_converter, link
6
+
7
+ from annet.adapters.netbox.common import models
8
+ from annet.adapters.netbox.common.manufacturer import (
9
+ is_supported, get_hw, get_breed,
10
+ )
11
+ from annet.adapters.netbox.common.query import NetboxQuery
12
+ from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
13
+ from annet.annlib.netdev.views.hardware import HardwareView
14
+ from annet.storage import Storage
15
+ from . import api_models
16
+ from .client import NetboxV37
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ @impl_converter(recipe=[
22
+ link(P[api_models.Device].name, P[models.NetboxDevice].hostname),
23
+ link(P[api_models.Device].name, P[models.NetboxDevice].fqdn),
24
+ ])
25
+ def extend_device_base(
26
+ device: api_models.Device,
27
+ interfaces: List[models.Interface],
28
+ hw: Optional[HardwareView],
29
+ breed: str,
30
+ neighbours_ids: List[int],
31
+ ) -> models.NetboxDevice:
32
+ ...
33
+
34
+
35
+ def extend_device(
36
+ device: api_models.Device,
37
+ ) -> models.NetboxDevice:
38
+ return extend_device_base(
39
+ device=device,
40
+ interfaces=[],
41
+ breed=get_breed(
42
+ device.device_type.manufacturer.name,
43
+ device.device_type.model,
44
+ ),
45
+ hw=get_hw(
46
+ device.device_type.manufacturer.name,
47
+ device.device_type.model,
48
+ ),
49
+ neighbours_ids=[],
50
+ )
51
+
52
+
53
+ @impl_converter
54
+ def extend_interface(
55
+ interface: api_models.Interface, ip_addresses: List[models.IpAddress],
56
+ ) -> models.Interface:
57
+ ...
58
+
59
+
60
+ class NetboxStorageV37(Storage):
61
+ def __init__(self, opts: Optional[NetboxStorageOpts] = None):
62
+ self.netbox = NetboxV37(
63
+ url=opts.url,
64
+ token=opts.token,
65
+ )
66
+
67
+ def __enter__(self):
68
+ return self
69
+
70
+ def __exit__(self, _, __, ___):
71
+ pass
72
+
73
+ def resolve_object_ids_by_query(self, query: NetboxQuery):
74
+ return [
75
+ d.id for d in self._load_devices(query)
76
+ ]
77
+
78
+ def resolve_fdnds_by_query(self, query: NetboxQuery):
79
+ return [
80
+ d.name for d in self._load_devices(query)
81
+ ]
82
+
83
+ def make_devices(
84
+ self,
85
+ query: NetboxQuery,
86
+ preload_neighbors=False,
87
+ use_mesh=None,
88
+ preload_extra_fields=False,
89
+ **kwargs,
90
+ ) -> List[models.NetboxDevice]:
91
+ device_ids = {
92
+ device.id: extend_device(device=device)
93
+ for device in self._load_devices(query)
94
+ }
95
+ if not device_ids:
96
+ return []
97
+
98
+ interfaces = self._load_interfaces(list(device_ids))
99
+ for interface in interfaces:
100
+ device_ids[interface.device.id].interfaces.append(interface)
101
+ return list(device_ids.values())
102
+
103
+ def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
104
+ return [
105
+ device
106
+ for device in self.netbox.all_devices(
107
+ name__ic=query.globs,
108
+ ).results
109
+ if _match_query(query, device)
110
+ if is_supported(device.device_type.manufacturer.name)
111
+ ]
112
+
113
+ def _load_interfaces(self, device_ids: List[int]) -> List[
114
+ models.Interface]:
115
+ interfaces = self.netbox.all_interfaces(device_id=device_ids)
116
+ extended_ifaces = {
117
+ interface.id: extend_interface(interface, [])
118
+ for interface in interfaces.results
119
+ }
120
+
121
+ ips = self.netbox.all_ip_addresses(interface_id=list(extended_ifaces))
122
+ for ip in ips.results:
123
+ extended_ifaces[ip.assigned_object_id].ip_addresses.append(ip)
124
+ return list(extended_ifaces.values())
125
+
126
+ def get_device(
127
+ self, obj_id, preload_neighbors=False, use_mesh=None,
128
+ **kwargs,
129
+ ) -> models.NetboxDevice:
130
+ device = self.netbox.get_device(obj_id)
131
+ res = extend_device(device=device)
132
+ res.interfaces = self._load_interfaces([device.id])
133
+ return res
134
+
135
+ def flush_perf(self):
136
+ pass
137
+
138
+
139
+ def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
140
+ for subquery in query.globs:
141
+ if subquery.strip() in device_data.name:
142
+ return True
143
+ return False
@@ -116,7 +116,7 @@ def get_op(line: str) -> typing.Tuple[str, str, str]:
116
116
  break
117
117
  if opidx >= 0:
118
118
  op = line[opidx]
119
- indent = line[:opidx] + line[opidx+1:rowstart]
119
+ indent = line[:opidx] + line[opidx + 1:rowstart]
120
120
  if op != " ":
121
121
  indent = indent + " "
122
122
  return op, indent, line[rowstart:]
annet/api/__init__.py CHANGED
@@ -188,7 +188,9 @@ def _print_pre_as_diff(pre, show_rules, indent, file=None, _level=0):
188
188
  def log_host_progress_cb(pool: Parallel, task_result: TaskResult):
189
189
  progress_logger = get_logger("progress")
190
190
  args = cast(cli_args.QueryOptions, pool.args[0])
191
- with storage_connector.get().storage()(args) as storage:
191
+ connector = storage_connector.get()
192
+ storage_opts = connector.opts().from_cli_opts(args)
193
+ with connector.storage()(storage_opts) as storage:
192
194
  hosts = storage.resolve_fdnds_by_query(args.query)
193
195
  perc = int(pool.tasks_done / len(hosts) * 100)
194
196
  fqdn = hosts[task_result.device_id]
@@ -210,7 +212,9 @@ def log_host_progress_cb(pool: Parallel, task_result: TaskResult):
210
212
  # =====
211
213
  def gen(args: cli_args.ShowGenOptions):
212
214
  """ Сгенерировать конфиг для устройств """
213
- with storage_connector.get().storage()(args) as storage:
215
+ connector = storage_connector.get()
216
+ storage_opts = connector.opts().from_cli_opts(args)
217
+ with connector.storage()(storage_opts) as storage:
214
218
  loader = ann_gen.Loader(storage, args)
215
219
  stdin = args.stdin(storage=storage, filter_acl=args.filter_acl, config=None)
216
220
 
@@ -243,7 +247,9 @@ def _diff_files(old_files, new_files, context=3):
243
247
  def patch(args: cli_args.ShowPatchOptions):
244
248
  """ Сгенерировать патч для устройств """
245
249
  global live_configs # pylint: disable=global-statement
246
- with storage_connector.get().storage()(args) as storage:
250
+ connector = storage_connector.get()
251
+ storage_opts = connector.opts().from_cli_opts(args)
252
+ with connector.storage()(storage_opts) as storage:
247
253
  loader = ann_gen.Loader(storage, args)
248
254
  if args.config == "running":
249
255
  fetcher = annet.deploy.fetcher_connector.get()
@@ -286,7 +292,9 @@ def _patch_worker(device_id, args: cli_args.ShowPatchOptions, stdin, loader: ann
286
292
  # =====
287
293
  def res_diff_patch(device_id, args: cli_args.ShowPatchOptions, stdin, loader: ann_gen.Loader, filterer: filtering.Filterer) -> Iterable[
288
294
  Tuple[OldNewResult, Dict, Dict]]:
289
- with storage_connector.get().storage()(args) as storage:
295
+ connector = storage_connector.get()
296
+ storage_opts = connector.opts().from_cli_opts(args)
297
+ with connector.storage()(storage_opts) as storage:
290
298
  for res in ann_gen.old_new(
291
299
  args,
292
300
  storage,
@@ -314,7 +322,9 @@ def res_diff_patch(device_id, args: cli_args.ShowPatchOptions, stdin, loader: an
314
322
 
315
323
  def diff(args: cli_args.DiffOptions, loader: ann_gen.Loader, filterer: filtering.Filterer) -> Mapping[Device, Union[Diff, PCDiff]]:
316
324
  ret = {}
317
- with storage_connector.get().storage()(args) as storage:
325
+ connector = storage_connector.get()
326
+ storage_opts = connector.opts().from_cli_opts(args)
327
+ with connector.storage()(storage_opts) as storage:
318
328
  for res in ann_gen.old_new(
319
329
  args,
320
330
  storage,
@@ -331,16 +341,15 @@ def diff(args: cli_args.DiffOptions, loader: ann_gen.Loader, filterer: filtering
331
341
  acl_rules = res.get_acl_rules(args.acl_safe)
332
342
  new_files = res.get_new_files(args.acl_safe)
333
343
  new_json_fragment_files = res.get_new_file_fragments()
344
+ pc_diff_files = []
334
345
  if res.old_files or new_files:
335
- ret[device] = PCDiff(
336
- hostname=device.hostname,
337
- diff_files=list(_pc_diff(device.hostname, res.old_files, new_files)),
338
- )
339
- elif res.old_json_fragment_files or new_json_fragment_files:
340
- ret[device] = PCDiff(
341
- hostname=device.hostname,
342
- diff_files=list(_json_fragment_diff(device.hostname, res.old_json_fragment_files, new_json_fragment_files)),
343
- )
346
+ pc_diff_files.extend(_pc_diff(device.hostname, res.old_files, new_files))
347
+ if res.old_json_fragment_files or new_json_fragment_files:
348
+ pc_diff_files.extend(_json_fragment_diff(device.hostname, res.old_json_fragment_files, new_json_fragment_files))
349
+
350
+ if pc_diff_files:
351
+ pc_diff_files.sort(key=lambda f: f.label)
352
+ ret[device] = PCDiff(hostname=device.hostname, diff_files=pc_diff_files)
344
353
  elif old is not None:
345
354
  rb = rulebook.get_rulebook(device.hw)
346
355
  diff_tree = patching.make_diff(old, new, rb, [acl_rules, res.filter_acl_rules])
@@ -625,7 +634,9 @@ def deploy(args: cli_args.DeployOptions) -> ExitCode:
625
634
  """ Сгенерировать конфиг для устройств и задеплоить его """
626
635
  ret: ExitCode = 0
627
636
  deployer = Deployer(args)
628
- with storage_connector.get().storage()(args) as storage:
637
+ connector = storage_connector.get()
638
+ storage_opts = connector.opts().from_cli_opts(args)
639
+ with connector.storage()(storage_opts) as storage:
629
640
  global live_configs # pylint: disable=global-statement
630
641
  loader = ann_gen.Loader(storage, args)
631
642
  filterer = filtering.filterer_connector.get()
@@ -761,7 +772,7 @@ def _json_fragment_diff(
761
772
 
762
773
  def guess_hw(config_text: str):
763
774
  """Пытаемся угадать вендора и hw на основе
764
- текста конфига и annushka/rulebook/texts/*.rul"""
775
+ текста конфига и annet/rulebook/texts/*.rul"""
765
776
  scores = {}
766
777
  hw_provider = hardware_connector.get()
767
778
  for vendor in VENDOR_REVERSES:
annet/cli.py CHANGED
@@ -54,7 +54,9 @@ def show_current(args: cli_args.QueryOptions, config, arg_out: cli_args.FileOutO
54
54
  entire_data = ""
55
55
  yield (output_driver.entire_config_dest_path(device, entire_path), entire_data, False)
56
56
 
57
- with storage_connector.get().storage()(args) as storage:
57
+ connector = storage_connector.get()
58
+ storage_opts = connector.opts().from_cli_opts(args)
59
+ with connector.storage()(storage_opts) as storage:
58
60
  ids = storage.resolve_object_ids_by_query(args.query)
59
61
  if not ids:
60
62
  get_logger().error("No devices found for %s", args.query)
@@ -80,7 +82,9 @@ def gen(args: cli_args.ShowGenOptions):
80
82
  @subcommand(cli_args.ShowDiffOptions)
81
83
  def diff(args: cli_args.ShowDiffOptions):
82
84
  """ Сгенерировать конфиг для устройств и показать дифф по рулбуку с текущим """
83
- with storage_connector.get().storage()(args) as storage:
85
+ connector = storage_connector.get()
86
+ storage_opts = connector.opts().from_cli_opts(args)
87
+ with connector.storage()(storage_opts) as storage:
84
88
  filterer = filtering.filterer_connector.get()
85
89
  loader = Loader(storage, args)
86
90
  output_driver_connector.get().write_output(
annet/cli_args.py CHANGED
@@ -433,6 +433,11 @@ class FileOutOptions(ArgGroup):
433
433
  no_label = opt_no_label
434
434
  no_color = opt_no_color
435
435
 
436
+ def __init__(self, *args, **kwargs):
437
+ super().__init__(*args, **kwargs)
438
+ if self.dest:
439
+ self.no_color = True
440
+
436
441
 
437
442
  class DiffOptions(GenOptions, ComocutorOptions):
438
443
  clear = opt_clear
@@ -467,6 +472,11 @@ class ShowDiffOptions(DiffOptions, FileOutOptions):
467
472
  show_rules = opt_show_rules
468
473
  no_collapse = opt_no_collapse
469
474
 
475
+ def __init__(self, *args, **kwargs):
476
+ super().__init__(*args, **kwargs)
477
+ if self.dest:
478
+ self.no_collapse = True
479
+
470
480
 
471
481
  class ShowPatchOptions(PatchOptions, FileOutOptions):
472
482
  indent = opt_indent
annet/deploy.py CHANGED
@@ -2,17 +2,22 @@
2
2
 
3
3
 
4
4
  import abc
5
+ import itertools
5
6
  import re
6
7
  from collections import namedtuple
7
8
  from contextlib import contextmanager
8
- from typing import Dict, List, Optional, Type
9
+ from typing import Dict, List, Optional, Type, Any, OrderedDict
9
10
 
10
11
  from contextlog import get_logger
11
12
 
12
13
  from annet import text_term_format
14
+ from annet.annlib.command import Command, Question, CommandList
15
+ from annet.annlib.netdev.views.hardware import HardwareView
16
+ from annet.annlib.rbparser.deploying import MakeMessageMatcher, Answer
13
17
  from annet.cli_args import DeployOptions
14
18
  from annet.connectors import Connector
15
19
  from annet.output import TextArgs
20
+ from annet.rulebook import get_rulebook, deploying
16
21
  from annet.storage import Device
17
22
 
18
23
 
@@ -439,3 +444,93 @@ def init_colors():
439
444
  None: curses.color_pair(8),
440
445
  "cyan_blue": curses.color_pair(9),
441
446
  }
447
+
448
+
449
+ class RulebookQuestionHandler:
450
+ def __init__(self, dialogs):
451
+ self._dialogs = dialogs
452
+
453
+ def __call__(self, dev: Connector, cmd: Command, match_content: bytes):
454
+ content = match_content.strip()
455
+ content = content.decode()
456
+ for matcher, answer in self._dialogs.items():
457
+ if matcher(content):
458
+ return Command(answer.text)
459
+
460
+ get_logger().info("no answer in rulebook. dialogs=%s match_content=%s", self._dialogs, match_content)
461
+ return None
462
+
463
+
464
+ def rb_question_to_question(q: MakeMessageMatcher, a: Answer) -> Question: # TODO: drop MakeMessageMatcher
465
+ if not a.send_nl:
466
+ raise Exception("not supported false send_nl")
467
+ text: str = q._text # pylint: disable=protected-access
468
+ is_regexp = False
469
+ if text.startswith("/") and text.endswith("/"):
470
+ is_regexp = True
471
+ text = text[1:-1]
472
+ res = Question(question=text, answer=a.text, is_regexp=is_regexp)
473
+ return res
474
+
475
+
476
+ def make_cmd_params(rule: Dict[str, Any]) -> Dict[str, Any]:
477
+ if rule:
478
+ qa_handler = RulebookQuestionHandler(rule["attrs"]["dialogs"])
479
+ qa_list: List[Question] = []
480
+ for matcher, answer in qa_handler._dialogs.items(): # pylint: disable=protected-access
481
+ qa_list.append(rb_question_to_question(matcher, answer))
482
+ return {
483
+ "questions": qa_list,
484
+ "timeout": rule["attrs"]["timeout"],
485
+ }
486
+ return {
487
+ "timeout": 30,
488
+ }
489
+
490
+
491
+ def make_apply_commands(rule, hw, do_commit, do_finalize):
492
+ apply_logic = rule["attrs"]["apply_logic"]
493
+ before, after = apply_logic(hw, do_commit=do_commit, do_finalize=do_finalize)
494
+ return before, after
495
+
496
+
497
+ def fill_cmd_params(rules: OrderedDict, cmd: Command):
498
+ rule = deploying.match_deploy_rule(rules, (cmd.cmd,), {})
499
+ if rule:
500
+ cmd_params = make_cmd_params(rule)
501
+ cmd.questions = cmd_params.get("questions", None)
502
+ cmd.timeout = cmd_params["timeout"]
503
+
504
+
505
+ def apply_deploy_rulebook(hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True):
506
+ rules = get_rulebook(hw)["deploying"]
507
+ cmds_with_apply = []
508
+ for cmd_path, context in cmd_paths.items():
509
+ rule = deploying.match_deploy_rule(rules, cmd_path, context)
510
+ cmd_params = make_cmd_params(rule)
511
+ before, after = make_apply_commands(rule, hw, do_commit, do_finalize)
512
+
513
+ cmd = Command(cmd_path[-1], **cmd_params)
514
+ # XXX более чистый способ передавать-мета инфу о команде
515
+ cmd.level = len(cmd_path) - 1
516
+ cmds_with_apply.append((cmd, before, after))
517
+
518
+ def _key(item):
519
+ _cmd, before, after = item
520
+ return (tuple(cmd.cmd for cmd in before), tuple(cmd.cmd for cmd in after))
521
+
522
+ cmdlist = CommandList()
523
+ for _k, cmd_before_after in itertools.groupby(cmds_with_apply, key=_key):
524
+ cmd_before_after = list(cmd_before_after)
525
+ _, before, after = cmd_before_after[0]
526
+ for c in before:
527
+ c.level = 0
528
+ fill_cmd_params(rules, c)
529
+ cmdlist.add_cmd(c)
530
+ for cmd, _before, _after in cmd_before_after:
531
+ cmdlist.add_cmd(cmd)
532
+ for c in after:
533
+ c.level = 0
534
+ fill_cmd_params(rules, c)
535
+ cmdlist.add_cmd(c)
536
+ return cmdlist
annet/diff.py CHANGED
@@ -30,8 +30,7 @@ def gen_sort_diff(
30
30
  :param diffs: Маппинг устройства в дифф
31
31
  :param args: Параметры коммандной строки
32
32
  """
33
- # NOCDEV-2201 non-null --dest implies --no-collapse
34
- if args.no_collapse or args.dest:
33
+ if args.no_collapse:
35
34
  devices_to_diff = {(dev,): diff for dev, diff in diffs.items()}
36
35
  else:
37
36
  non_pc_diffs = {dev: diff for dev, diff in diffs.items() if not isinstance(diff, PCDiff)}
annet/gen.py CHANGED
@@ -425,7 +425,9 @@ def worker(device_id, args: ShowGenOptions, stdin, loader: "Loader", filterer: F
425
425
  if span:
426
426
  span.set_attribute("device.id", device_id)
427
427
 
428
- with storage_connector.get().storage()(args) as storage:
428
+ connector = storage_connector.get()
429
+ storage_opts = connector.opts().from_cli_opts(args)
430
+ with connector.storage()(storage_opts) as storage:
429
431
  for res in old_new(
430
432
  args,
431
433
  storage,
@@ -465,7 +467,9 @@ def worker(device_id, args: ShowGenOptions, stdin, loader: "Loader", filterer: F
465
467
 
466
468
 
467
469
  def old_new_worker(device_id, args: DeployOptions, config, stdin, loader: "Loader", filterer: Filterer):
468
- with storage_connector.get().storage()(args) as storage:
470
+ connector = storage_connector.get()
471
+ storage_opts = connector.opts().from_cli_opts(args)
472
+ with connector.storage()(storage_opts) as storage:
469
473
  yield from old_new(
470
474
  args,
471
475
  storage,
@@ -693,7 +697,7 @@ def _old_new_get_config_cli(ctx: OldNewDeviceContext, device: Device) -> str:
693
697
  def _old_new_get_config_files(ctx: OldNewDeviceContext, device: Device) -> DeviceDownloadedFiles:
694
698
  old_files = DeviceDownloadedFiles()
695
699
 
696
- for attr in ('failed_files', 'failed_running', 'failed_packages'):
700
+ for attr in ("failed_files", "failed_running", "failed_packages"):
697
701
  if device in getattr(ctx, attr):
698
702
  exc = getattr(ctx, attr).get(device)
699
703
  exc = exc or Exception(f"I can't get device {attr[len('failed_'):]} and I don't know why")
@@ -538,15 +538,7 @@ def _get_generators(module_paths: Union[List[str], dict], storage, device=None):
538
538
  module = importlib.import_module(module_path)
539
539
  if hasattr(module, "get_generators"):
540
540
  generators: List[BaseGenerator] = module.get_generators(storage)
541
- if device is None:
542
- res_generators += generators
543
- else:
544
- logger = get_logger()
545
- for gen in generators:
546
- if gen.supports_vendor(device.hw.vendor):
547
- res_generators.append(gen)
548
- else:
549
- logger.info("generator %s does not support device vendor %s, skipping", gen, device.hw.vendor)
541
+ res_generators += generators
550
542
  return res_generators
551
543
 
552
544
 
@@ -706,6 +698,12 @@ class PartialGenerator(TreeGenerator):
706
698
  def run(self, device) -> Iterable[Union[str, tuple]]:
707
699
  if hasattr(self, "run_" + device.hw.vendor):
708
700
  return getattr(self, "run_" + device.hw.vendor)(device)
701
+ logger = get_logger()
702
+ logger.info(
703
+ "generator %s is not supported for vendor %s",
704
+ self,
705
+ device.hw.vendor,
706
+ )
709
707
  return iter(())
710
708
 
711
709
  def get_user_runner(self, device):
annet/output.py CHANGED
@@ -133,7 +133,9 @@ class OutputDriverBasic(OutputDriver):
133
133
  ret = []
134
134
  fqdns = {}
135
135
  if args:
136
- with storage_connector.get().storage()(args) as storage:
136
+ connector = storage_connector.get()
137
+ storage_opts = connector.opts().from_cli_opts(args)
138
+ with connector.storage()(storage_opts) as storage:
137
139
  fqdns = storage.resolve_fdnds_by_query(args.query)
138
140
  for (assignment, exc) in fail.items():
139
141
  label = assignment
@@ -4,15 +4,15 @@ from annet.annlib.types import Op
4
4
 
5
5
 
6
6
  try:
7
- from annet.executor import CommandList
7
+ from annet.executor import CommandList, Command
8
8
  except ImportError:
9
- from noc.annushka.annet.executor import CommandList
9
+ from noc.annushka.annet.executor import CommandList, Command
10
10
 
11
11
 
12
12
  def apply(hw, do_commit, do_finalize, **_):
13
13
  before, after = CommandList(), CommandList()
14
14
  if do_commit:
15
- after.add_cmd("write memory")
15
+ after.add_cmd(Command("write memory"))
16
16
  return (before, after)
17
17
 
18
18
 
@@ -325,7 +325,7 @@ interface * %logic=huawei.iface.permanent
325
325
  trust upstream
326
326
  %if hw.Huawei.CE:
327
327
  trust *
328
- %else
328
+ %else:
329
329
  trust
330
330
  %endif
331
331
  dhcp snooping check dhcp-rate enable
@@ -442,7 +442,7 @@ snmp-agent trap source
442
442
  snmp-agent packet max-size
443
443
  %if hw.Huawei.Quidway:
444
444
  snmp-agent protocol source-interface %logic=huawei.misc.undo_redo
445
- %else
445
+ %else:
446
446
  snmp-agent protocol source-interface *
447
447
  %endif
448
448
  snmp-agent ~
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.5
3
+ Version: 0.7
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -21,4 +21,6 @@ Requires-Dist: contextlog >=1.1
21
21
  Requires-Dist: valkit >=0.1.4
22
22
  Requires-Dist: aiohttp >=3.8.4
23
23
  Requires-Dist: yarl >=1.8.2
24
+ Requires-Dist: adaptix ==3.0.0b2
25
+ Requires-Dist: dataclass-rest ==0.4
24
26