annet 0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
@@ -0,0 +1,114 @@
1
+ import re
2
+ from abc import ABC
3
+ from os import path
4
+ from typing import Iterable, Union
5
+
6
+ from annet.annlib.lib import mako_render
7
+ from annet.annlib.rbparser.ordering import compile_ordering_text
8
+ from annet.annlib.rbparser.platform import VENDOR_REVERSES
9
+
10
+ from annet.connectors import CachedConnector
11
+ from annet.rulebook.deploying import compile_deploying_text
12
+ from annet.rulebook.patching import compile_patching_text
13
+
14
+
15
+ class RulebookProvider(ABC):
16
+ def get_rulebook(self, hw):
17
+ raise NotImplementedError
18
+
19
+ def get_root_modules(self) -> Iterable[str]:
20
+ raise NotImplementedError
21
+
22
+
23
+ class _RulebookProviderConnector(CachedConnector[RulebookProvider]):
24
+ name = "Rulebook provider"
25
+ ep_name = "rulebook"
26
+
27
+
28
+ rulebook_provider_connector = _RulebookProviderConnector()
29
+
30
+
31
+ def get_rulebook(hw):
32
+ return rulebook_provider_connector.get().get_rulebook(hw)
33
+
34
+
35
+ class DefaultRulebookProvider(RulebookProvider):
36
+ root_dir = (path.dirname(__file__),)
37
+ root_modules = ("annet.rulebook",)
38
+
39
+ def __init__(self, root_dir: Union[str, Iterable[str], None] = None,
40
+ root_modules: Union[str, Iterable[str], None] = None):
41
+ self._rulebook_cache = {}
42
+ self._render_rul_cache = {}
43
+ self._escaped_rul_cache = {}
44
+
45
+ if root_dir is None:
46
+ pass
47
+ elif isinstance(root_dir, str):
48
+ self.root_dir = (root_dir,)
49
+ else:
50
+ self.root_dir = tuple(root_dir)
51
+
52
+ if root_modules is None:
53
+ pass
54
+ elif isinstance(root_modules, str):
55
+ self.root_modules = (root_modules,)
56
+ else:
57
+ self.root_modules = tuple(root_modules)
58
+
59
+ def get_root_modules(self):
60
+ return self.root_modules
61
+
62
+ def get_rulebook(self, hw):
63
+ if hw in self._rulebook_cache:
64
+ return self._rulebook_cache[hw]
65
+
66
+ assert hw.vendor in VENDOR_REVERSES, "Unknown vendor: %s" % (hw.vendor)
67
+ patching = compile_patching_text(self._render_rul(hw.vendor + ".rul", hw), hw.vendor)
68
+
69
+ try:
70
+ ordering_text = self._render_rul(hw.vendor + ".order", hw)
71
+ except FileNotFoundError:
72
+ ordering_text = ""
73
+ ordering = compile_ordering_text(ordering_text, hw.vendor)
74
+
75
+ try:
76
+ deploying_text = self._render_rul(hw.vendor + ".deploy", hw)
77
+ except FileNotFoundError:
78
+ deploying_text = ""
79
+
80
+ deploying = compile_deploying_text(deploying_text, hw.vendor)
81
+
82
+ self._rulebook_cache[hw] = {
83
+ "patching": patching,
84
+ "ordering": ordering,
85
+ "deploying": deploying,
86
+ }
87
+ return self._rulebook_cache[hw]
88
+
89
+ def _render_rul(self, name, hw):
90
+ key = (name, hw)
91
+ if key not in self._render_rul_cache:
92
+ self._render_rul_cache[key] = mako_render(self._read_escaped_rul(name), hw=hw)
93
+ return self._render_rul_cache[key]
94
+
95
+ def _read_escaped_rul(self, name):
96
+ if name in self._escaped_rul_cache:
97
+ return self._escaped_rul_cache[name]
98
+ for root_dir in self.root_dir:
99
+ try:
100
+ with open(path.join(root_dir, "texts", name), "r") as f:
101
+ self._escaped_rul_cache[name] = self._escape_mako(f.read())
102
+ return self._escaped_rul_cache[name]
103
+ except FileNotFoundError:
104
+ pass
105
+
106
+ raise FileNotFoundError(f"Unable to find rul: {name}")
107
+
108
+ @staticmethod
109
+ def _escape_mako(text):
110
+ # Экранирование всего, что начинается на %, например %comment -> %%comment, чтобы он не интерпретировался
111
+ # как mako-оператор
112
+ text = re.sub(r"(?:^|\n)%((?!if\s*|elif\s*|else\s*|endif\s*|for\s*|endfor\s*))", "\n%%\\1", text)
113
+ text = re.sub(r"(?:^|\n)\s*#.*", "", text)
114
+ return text
File without changes
@@ -0,0 +1,16 @@
1
+ import re
2
+
3
+ from annet.rulebook import common
4
+
5
+
6
+ # Добавление возможности удаления агрегатов, лупбэков, SVI и сабинтерфейсов
7
+ def permanent(rule, key, diff, **kwargs): # pylint: disable=redefined-outer-name
8
+ ifname = key[0]
9
+ # Match group examples:
10
+ # Group 01: Port-Channel10, Loopback1, Vlan800
11
+ # Group 02: Ethernet2/1.20, Port-Channel10.200
12
+ if re.match(r"((?:Port-Channel|Loopback|Vlan)\d+$)|((?:Ethernet|Port-Channel)[\d/]+\.\d+$)", ifname):
13
+ # Эти интерфейсы можно удалять
14
+ yield from common.default(rule, key, diff, **kwargs)
15
+ else:
16
+ yield from common.permanent(rule, key, diff, **kwargs)
@@ -0,0 +1,16 @@
1
+ from annet.annlib.rulebook import common
2
+ from annet.annlib.types import Op
3
+
4
+
5
+ def default_diff(old, new, diff_pre, _pops=(Op.AFFECTED,)):
6
+ diff = common.base_diff(old, new, diff_pre, _pops, moved_to_affected=True)
7
+ diff[:] = _skip_non_ap_env_affected(diff)
8
+ return diff
9
+
10
+
11
+ def _skip_non_ap_env_affected(diff):
12
+ for x in diff:
13
+ if x.op == Op.AFFECTED and not x.children:
14
+ if x.diff_pre["attrs"]["context"].get("block") != "ap-env":
15
+ continue
16
+ yield x
@@ -0,0 +1,146 @@
1
+ # pylint: disable=unused-argument
2
+
3
+ from annet.annlib.types import Op
4
+
5
+
6
+ try:
7
+ from annet.executor import CommandList, Command
8
+ except ImportError:
9
+ from noc.annushka.annet.executor import CommandList, Command
10
+
11
+
12
+ def apply(hw, do_commit, do_finalize, **_):
13
+ before, after = CommandList(), CommandList()
14
+ if do_commit:
15
+ after.add_cmd(Command("write memory"))
16
+ return (before, after)
17
+
18
+
19
+ def patch_flag(rule, key, diff, **_):
20
+ direct, cmd = None, ""
21
+ if diff[Op.ADDED]:
22
+ row, _ = diff[Op.ADDED][0]["row"].split(":")
23
+ cmd = row.replace("_", "-")
24
+ direct = True
25
+ elif diff[Op.REMOVED]:
26
+ row, _ = diff[Op.REMOVED][0]["row"].split(":")
27
+ cmd = "no " + row.replace("_", "-")
28
+ direct = False
29
+ if cmd:
30
+ yield direct, cmd, None
31
+
32
+
33
+ def hostname(rule, key, diff, **_):
34
+ if diff[Op.ADDED]:
35
+ yield True, "hostname %s" % key, None
36
+
37
+
38
+ def mgmt(rule, key, diff, rule_pre, **_):
39
+ if not diff[Op.ADDED] and not diff[Op.REMOVED]:
40
+ return
41
+ pre_items = rule_pre["items"]
42
+ unchanged = {k[0]: v[Op.UNCHANGED][0]["row"].split(":")[1] for k, v in pre_items.items() if v[Op.UNCHANGED]}
43
+ added = {k[0]: v[Op.ADDED][0]["row"].split(":")[1] for k, v in pre_items.items() if v[Op.ADDED]}
44
+ params = {
45
+ "ipaddr": None,
46
+ "netmask": None,
47
+ "gatewayip": None,
48
+ "dnsip": None,
49
+ "domainname": None,
50
+ }
51
+ params.update({k: v for k, v in unchanged.items() if k in params})
52
+ params.update({k: v for k, v in added.items() if k in params})
53
+ empty = {k: v for k, v in params.items() if v is None}
54
+ if empty:
55
+ raise RuntimeError("Failed to determine params %s" % ",".join(empty.keys()))
56
+ yield True, f"ip-address {params['ipaddr']} {params['netmask']} {params['gatewayip']} {params['dnsip']} {params['domainname']}", None
57
+
58
+
59
+ def swarm_mode(rule, key, diff, **_):
60
+ if diff[Op.ADDED]:
61
+ row = diff[Op.ADDED][0]["row"]
62
+ mode = row.split("_")[0]
63
+ yield True, "swarm-mode %s" % mode, None
64
+ elif diff[Op.REMOVED]:
65
+ yield True, "swarm-mode cluster", None
66
+
67
+
68
+ def iap_zone(rule, key, diff, **_):
69
+ if diff[Op.ADDED]:
70
+ yield True, "zone %s" % key, None
71
+
72
+
73
+ def dot11_radio(rule, key, diff, **_):
74
+ direct, cmd = None, ""
75
+ if diff[Op.ADDED]:
76
+ direct, cmd = True, diff[Op.ADDED][0]["row"]
77
+ elif diff[Op.REMOVED]:
78
+ direct, cmd = False, "no " + diff[Op.REMOVED][0]["row"]
79
+ if cmd:
80
+ cmd = cmd.replace("_", "-")
81
+ cmd = cmd.replace(":", "-")
82
+ yield direct, cmd, None
83
+
84
+
85
+ def installation_type(rule, key, diff, **_):
86
+ if diff[Op.ADDED]:
87
+ row = diff[Op.ADDED][0]["row"]
88
+ _, installation_place = row.split(":")
89
+ yield True, "ap-installation %s" % installation_place, None
90
+ elif diff[Op.REMOVED]:
91
+ yield True, "ap-installation default", None
92
+
93
+
94
+ def wifi_arm(rule, key, diff, root_pre, **_):
95
+ if key[0].startswith("wifi0"):
96
+ prefix, cmd = "wifi0", "a-channel"
97
+ elif key[0].startswith("wifi1"):
98
+ prefix, cmd = "wifi1", "g-channel"
99
+ else:
100
+ raise ValueError("Unknown wifi channel key %r" % key)
101
+ pre_items = list(root_pre.values())[0]["items"]
102
+ unchanged = {k[0]: v[Op.UNCHANGED][0]["row"] for k, v in pre_items.items() if v[Op.UNCHANGED]}
103
+ added = {k[0]: v[Op.ADDED][0]["row"] for k, v in pre_items.items() if v[Op.ADDED]}
104
+ key_arm_channel = prefix + "_arm_channel"
105
+ key_arm_power = prefix + "_arm_power_10x"
106
+ arm_channel, arm_power = "0", "0"
107
+ for params in [unchanged, added]:
108
+ if key_arm_channel in params:
109
+ _, arm_channel = params[key_arm_channel].split(":")
110
+ if key_arm_power in params:
111
+ _, arm_power = params[key_arm_power].split(":")
112
+ yield True, f"{cmd} {arm_channel} {arm_power}", None
113
+
114
+
115
+ def ant_gain(rule, key, diff, root_pre, **_):
116
+ row, value, direct = "", "", None
117
+ if diff[Op.ADDED]:
118
+ row = diff[Op.ADDED][0]["row"]
119
+ _, value = row.split(":")
120
+ direct = True
121
+ elif diff[Op.REMOVED]:
122
+ row = diff[Op.REMOVED][0]["row"]
123
+ value = "0"
124
+ direct = False
125
+ if row:
126
+ if row.startswith("a_"):
127
+ cmd = "a-external-antenna"
128
+ elif row.startswith("g_"):
129
+ cmd = "g-external-antenna"
130
+ else:
131
+ raise ValueError("Unknown row '%s'" % row)
132
+ yield direct, f"{cmd} {value}", None
133
+
134
+
135
+ def ant_pol(rule, key, diff, root_pre, **_):
136
+ row, value, direct = "", "", None
137
+ if diff[Op.ADDED]:
138
+ row, value = diff[Op.ADDED][0]["row"].split(":")
139
+ direct = True
140
+ elif diff[Op.REMOVED]:
141
+ row, _ = diff[Op.REMOVED][0]["row"].split(":")
142
+ value = "0"
143
+ direct = False
144
+ if row:
145
+ cmd = row.replace("_", "-")
146
+ yield direct, f"{cmd} {value}", None
@@ -0,0 +1,8 @@
1
+ from annet.annlib.rulebook import common
2
+ from annet.annlib.types import Op
3
+
4
+
5
+ def syslog_level(rule, key, diff, **_):
6
+ # syslog-level can be overwritten only
7
+ if diff[Op.ADDED]:
8
+ yield from common.default(rule, key, diff)
File without changes
@@ -0,0 +1,68 @@
1
+ from annet.annlib.types import Op
2
+
3
+ from annet.rulebook import common
4
+
5
+
6
+ def diff(old, new, diff_pre, _pops=(Op.AFFECTED,)):
7
+ for iface_row in old:
8
+ _filter_channel_members(old[iface_row])
9
+ for iface_row in new:
10
+ _filter_channel_members(new[iface_row])
11
+
12
+ ret = common.default_diff(old, new, diff_pre, _pops)
13
+ vpn_changed = False
14
+ for (op, cmd, _, _) in ret:
15
+ if op in {Op.ADDED, Op.REMOVED}:
16
+ vpn_changed |= is_vpn_cmd(cmd)
17
+ if vpn_changed:
18
+ for cmd in list(old.keys()):
19
+ if is_ip_cmd(cmd) and not is_vpn_cmd(cmd):
20
+ del old[cmd]
21
+ ret = common.default_diff(old, new, diff_pre, _pops)
22
+ return ret
23
+
24
+
25
+ def is_vpn_cmd(cmd):
26
+ return cmd.startswith("vrf member")
27
+
28
+
29
+ def is_ip_cmd(cmd):
30
+ return cmd.startswith(("ip ", "ipv6 "))
31
+
32
+ # ===
33
+
34
+ # Вырезает все команды не разрешенные
35
+ # на членах агрегата. В running-config
36
+ # листинге они наследуются от самого port-channel
37
+
38
+
39
+ def _filter_channel_members(tree):
40
+ if any(is_in_channel(x) for x in tree):
41
+ for cmd in list(tree.keys()):
42
+ if not _is_allowed_on_channel(cmd):
43
+ del tree[cmd]
44
+
45
+
46
+ def is_in_channel(cmd_line):
47
+ """
48
+ Признак того, что это lagg member
49
+ """
50
+ return cmd_line.startswith("channel-group")
51
+
52
+
53
+ # Возможно тут есть еще какие-то команды
54
+ def _is_allowed_on_channel(cmd_line):
55
+ return cmd_line.startswith((
56
+ "channel-group",
57
+ "cdp",
58
+ "description",
59
+ "inherit",
60
+ "ip port",
61
+ "ipv6 port",
62
+ "mac port",
63
+ "lacp",
64
+ "switchport host",
65
+ "shutdown",
66
+ "rate-limit cpu",
67
+ "snmp trap link-status",
68
+ ))
@@ -0,0 +1,57 @@
1
+ import re
2
+
3
+ from annet.annlib.types import Op
4
+
5
+ from annet.rulebook import common
6
+
7
+
8
+ def ssh_key(rule, key, diff, hw, **_):
9
+ """
10
+ При включении ssh надо еще сгенерировать ключ. По конфигу никак не понять есть ли ключ на свитче или нет.
11
+ """
12
+ if diff[Op.ADDED]:
13
+ added = sorted([x["row"] for x in diff[Op.ADDED]])
14
+ if added == ["ip ssh version 2"]:
15
+ # Отсыпаем mpdaemon-у подсказок для дополнительной команды при наливке
16
+ comment = rule["comment"]
17
+ rule["comment"] = ["!!suppress_errors!!", "!!timeout=240!!"]
18
+ if hw.Cisco.C2960:
19
+ yield (False, "crypto key generate rsa modulus 2048", None)
20
+ else:
21
+ yield (False, "crypto key generate rsa general-keys modulus 2048", None)
22
+ rule["comment"] = comment
23
+ yield from common.default(rule, key, diff)
24
+
25
+
26
+ def no_ipv6_nd_suppress_ra(rule, key, diff, **_):
27
+ """
28
+ При конфигурации ipv6 nd на нексусах нужно добавлять
29
+ no ipv6 nd suppress-ra
30
+ иначе RA не будет включен.
31
+ К сожалению данной команды не видно в running-config.
32
+ Поэтому подмешиваем ее в патч вместо генератора
33
+ """
34
+ if diff[Op.ADDED]:
35
+ yield (False, "no ipv6 nd suppress-ra", None)
36
+ yield from common.default(rule, key, diff)
37
+
38
+
39
+ def no_ntp_distribute(rule, key, diff, **_):
40
+ """
41
+ Для того, чтобы удалить NTP из CFS, сначала нужно сбросить активные
42
+ NTP сессии.
43
+ """
44
+ if diff[Op.REMOVED]:
45
+ yield (False, "clear ntp session", None)
46
+ yield from common.default(rule, key, diff)
47
+
48
+
49
+ def banner_login(rule, key, diff, **_):
50
+ if diff[Op.REMOVED]:
51
+ yield (False, "no banner login", None)
52
+ elif diff[Op.ADDED]:
53
+ # Убираем дополнительный экранирующий сиимвол
54
+ key = re.sub(r"\^C", "^", key[0])
55
+ yield (False, f"banner login {key}", None)
56
+ else:
57
+ yield from common.default(rule, key, diff)
@@ -0,0 +1,90 @@
1
+ import re
2
+
3
+ from annet.annlib.lib import cisco_collapse_vlandb as collapse_vlandb
4
+ from annet.annlib.lib import cisco_expand_vlandb as expand_vlandb
5
+ from annet.annlib.types import Op
6
+
7
+
8
+ VLANDB_CHUNK = 15
9
+ SWTRUNK_CHUNK = 5
10
+
11
+
12
+ # =====
13
+ def simple(rule, key, diff, hw, **_):
14
+ yield from _process_vlandb(rule, key, diff, hw, False, VLANDB_CHUNK)
15
+
16
+
17
+ def swtrunk(rule, key, diff, hw, **_):
18
+ yield from _process_vlandb(rule, key, diff, hw, True, SWTRUNK_CHUNK)
19
+
20
+
21
+ # =====
22
+ def _process_vlandb(rule, key, diff, hw, explicit_changing, multi_chunk):
23
+ # pylint: disable=unused-argument
24
+ for affected in diff[Op.AFFECTED]:
25
+ # Изменилось содержимое блока vlan
26
+ yield (True, affected["row"], affected["children"])
27
+
28
+ (prefix, new, new_blocks) = _parse_vlancfg_actions(diff[Op.ADDED])
29
+ (prefix2, old, old_blocks) = _parse_vlancfg_actions(diff[Op.REMOVED])
30
+ if not prefix:
31
+ prefix = prefix2
32
+
33
+ if len(diff[Op.ADDED]) == 1 and len(new) == 0:
34
+ # switchport trunk allowed vlan none
35
+ yield (True, "%s none" % prefix, None)
36
+ return
37
+ for vlan_id in ((set(old_blocks.keys()) - set(new_blocks)) & new):
38
+ # Удалено содержимое блока vlan, но сам влан остался
39
+ yield (True, "%s %s" % (prefix, vlan_id), old_blocks[vlan_id])
40
+
41
+ removed = old.difference(new)
42
+ added = new.difference(old)
43
+ if hw.Catalyst:
44
+ # Каталисты не перечисляют вланы в batch режиме, если они представлены как блоки
45
+ added -= new_blocks.keys()
46
+
47
+ if removed:
48
+ collapsed = collapse_vlandb(removed, hw.Catalyst)
49
+ for chunk in _chunked(collapsed, multi_chunk):
50
+ yield (False, "no %s%s%s" % (prefix, " remove " if explicit_changing else " ", ",".join(chunk)), None)
51
+
52
+ if added:
53
+ collapsed = collapse_vlandb(added, hw.Catalyst)
54
+ for chunk in _chunked(collapsed, multi_chunk):
55
+ yield (True, "%s%s%s" % (prefix, " add " if explicit_changing else " ", ",".join(chunk)), None)
56
+ if new_blocks:
57
+ for vlan_id, block in new_blocks.items():
58
+ yield (True, "%s %s" % (prefix, vlan_id), block)
59
+
60
+
61
+ def _chunked(items, size):
62
+ for offset in range(0, len(items), size):
63
+ yield items[offset:offset + size]
64
+
65
+
66
+ def _parse_vlancfg_actions(actions):
67
+ prefix = None
68
+ vlandb = set()
69
+ blocks = {}
70
+ for action in actions:
71
+ (prefix, part) = _parse_vlancfg(action["row"])
72
+ if action["children"]:
73
+ assert len(part) == 1, "vlandb block must contain one and only one vlanid: %s" % action["row"]
74
+ blocks[list(part)[0]] = action["children"]
75
+ vlandb.update(part)
76
+ return (prefix, vlandb, blocks)
77
+
78
+
79
+ def _parse_vlancfg(row):
80
+ # иногда циски ставят пробелы между влан ренджами, а иногда нет.
81
+ words = re.sub(r",\s+", ",", row).split()
82
+
83
+ if words[-1] == "none":
84
+ # switchport trunk allowed vlan none
85
+ return (" ".join(words[:-1]), set())
86
+ assert re.match(r"[\d,-]+$", words[-1]), "Unable to parse vlancfg row: %s" % row
87
+ prefix = " ".join(words[:-2] if words[-2] == "add" else words[:-1])
88
+ vlancfg = words[-1]
89
+ vlandb = expand_vlandb(vlancfg)
90
+ return (prefix, vlandb)
@@ -0,0 +1,19 @@
1
+ import functools
2
+ import importlib
3
+
4
+ from annet.annlib.rulebook import common # pylint: disable=unused-import # noqa: F401,F403
5
+ from annet.annlib.rulebook.common import * # pylint: disable=wildcard-import,unused-wildcard-import # noqa: F401,F403
6
+
7
+
8
+ @functools.lru_cache()
9
+ def import_rulebook_function(name):
10
+ from . import rulebook_provider_connector
11
+
12
+ index = name.rindex(".")
13
+ for root in rulebook_provider_connector.get().get_root_modules():
14
+ try:
15
+ module = importlib.import_module(f"{root}.{name[:index]}", package=__name__.rsplit(".", 1)[0])
16
+ return getattr(module, name[index + 1:])
17
+ except ImportError:
18
+ pass
19
+ raise ImportError(f"Could not import {name}")
@@ -0,0 +1,87 @@
1
+ import functools
2
+ from collections import OrderedDict as odict
3
+ from collections import namedtuple
4
+
5
+ from annet.annlib.rbparser import platform, syntax
6
+ from annet.annlib.rbparser.deploying import compile_messages
7
+ from valkit.common import valid_bool, valid_number, valid_string_list
8
+ from valkit.python import valid_object_path
9
+
10
+ from .common import import_rulebook_function
11
+
12
+
13
+ Answer = namedtuple("Answer", ("text", "send_nl"))
14
+ DEFAULT_TIMEOUT = 30
15
+ DEFAULT_SEND_NL = True
16
+ DEFAULT_APPLY_LOGIC = "common.apply"
17
+
18
+
19
+ # =====
20
+ @functools.lru_cache()
21
+ def compile_deploying_text(text, vendor):
22
+ return _compile_deploying(
23
+ tree=syntax.parse_text(text, params_scheme={
24
+ "timeout": {
25
+ "validator": lambda arg: valid_number(arg, min=1, type=float),
26
+ "default": 30,
27
+ },
28
+ "send_nl": {
29
+ "validator": valid_bool,
30
+ "default": True,
31
+ },
32
+ "apply_logic": {
33
+ "validator": valid_object_path,
34
+ "default": DEFAULT_APPLY_LOGIC,
35
+ },
36
+ "ifcontext": {
37
+ "validator": valid_string_list,
38
+ "default": [],
39
+ }
40
+ }),
41
+ reverse_prefix=platform.VENDOR_REVERSES[vendor],
42
+ )
43
+
44
+
45
+ # =====
46
+ def _compile_deploying(tree, reverse_prefix):
47
+ deploying = odict()
48
+ for (rule_id, attrs) in tree.items():
49
+ if attrs["type"] == "normal" and not attrs["row"].startswith(("ignore:", "dialog:")):
50
+ (ignore, dialogs) = compile_messages(attrs["children"])
51
+ deploying[rule_id] = {
52
+ "attrs": {
53
+ "regexp": syntax.compile_row_regexp(attrs["row"]),
54
+ "timeout": attrs["params"]["timeout"],
55
+ "apply_logic": import_rulebook_function(attrs["params"]["apply_logic"]),
56
+ "ignore": ignore,
57
+ "dialogs": dialogs,
58
+ "ifcontext": attrs["params"]["ifcontext"],
59
+ },
60
+ "children": _compile_deploying(attrs["children"], reverse_prefix),
61
+ }
62
+ return deploying
63
+
64
+
65
+ def match_deploy_rule(rules, cmd_path, context):
66
+ for (depth, row) in enumerate(cmd_path):
67
+ for rule in rules.values():
68
+ if rule["attrs"]["regexp"].match(row):
69
+ ifcontext = rule["attrs"]["ifcontext"]
70
+ if syntax.match_context(ifcontext, context):
71
+ if depth == len(cmd_path) - 1:
72
+ return rule
73
+ else:
74
+ rules = rule["children"]
75
+ if len(rules) == 0:
76
+ break
77
+ # default match
78
+ return {
79
+ "attrs": {
80
+ "regexp": syntax.compile_row_regexp("~"),
81
+ "timeout": DEFAULT_TIMEOUT,
82
+ "apply_logic": import_rulebook_function(DEFAULT_APPLY_LOGIC),
83
+ "ignore": [],
84
+ "dialogs": odict(),
85
+ },
86
+ "children": odict(),
87
+ }
File without changes