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.
- annet/__init__.py +61 -0
- annet/adapters/__init__.py +0 -0
- annet/adapters/netbox/__init__.py +0 -0
- annet/adapters/netbox/common/__init__.py +0 -0
- annet/adapters/netbox/common/client.py +87 -0
- annet/adapters/netbox/common/manufacturer.py +62 -0
- annet/adapters/netbox/common/models.py +105 -0
- annet/adapters/netbox/common/query.py +23 -0
- annet/adapters/netbox/common/status_client.py +25 -0
- annet/adapters/netbox/common/storage_opts.py +14 -0
- annet/adapters/netbox/provider.py +34 -0
- annet/adapters/netbox/v24/__init__.py +0 -0
- annet/adapters/netbox/v24/api_models.py +73 -0
- annet/adapters/netbox/v24/client.py +59 -0
- annet/adapters/netbox/v24/storage.py +196 -0
- annet/adapters/netbox/v37/__init__.py +0 -0
- annet/adapters/netbox/v37/api_models.py +38 -0
- annet/adapters/netbox/v37/client.py +62 -0
- annet/adapters/netbox/v37/storage.py +149 -0
- annet/annet.py +25 -0
- annet/annlib/__init__.py +7 -0
- annet/annlib/command.py +49 -0
- annet/annlib/diff.py +158 -0
- annet/annlib/errors.py +8 -0
- annet/annlib/filter_acl.py +196 -0
- annet/annlib/jsontools.py +116 -0
- annet/annlib/lib.py +495 -0
- annet/annlib/netdev/__init__.py +0 -0
- annet/annlib/netdev/db.py +62 -0
- annet/annlib/netdev/devdb/__init__.py +28 -0
- annet/annlib/netdev/devdb/data/devdb.json +137 -0
- annet/annlib/netdev/views/__init__.py +0 -0
- annet/annlib/netdev/views/dump.py +121 -0
- annet/annlib/netdev/views/hardware.py +112 -0
- annet/annlib/output.py +246 -0
- annet/annlib/patching.py +533 -0
- annet/annlib/rbparser/__init__.py +0 -0
- annet/annlib/rbparser/acl.py +120 -0
- annet/annlib/rbparser/deploying.py +55 -0
- annet/annlib/rbparser/ordering.py +52 -0
- annet/annlib/rbparser/platform.py +51 -0
- annet/annlib/rbparser/syntax.py +115 -0
- annet/annlib/rulebook/__init__.py +0 -0
- annet/annlib/rulebook/common.py +350 -0
- annet/annlib/tabparser.py +648 -0
- annet/annlib/types.py +35 -0
- annet/api/__init__.py +826 -0
- annet/argparse.py +415 -0
- annet/cli.py +237 -0
- annet/cli_args.py +503 -0
- annet/configs/context.yml +18 -0
- annet/configs/logging.yaml +39 -0
- annet/connectors.py +77 -0
- annet/deploy.py +536 -0
- annet/diff.py +84 -0
- annet/executor.py +551 -0
- annet/filtering.py +40 -0
- annet/gen.py +865 -0
- annet/generators/__init__.py +435 -0
- annet/generators/base.py +136 -0
- annet/generators/common/__init__.py +0 -0
- annet/generators/common/initial.py +33 -0
- annet/generators/entire.py +97 -0
- annet/generators/exceptions.py +10 -0
- annet/generators/jsonfragment.py +125 -0
- annet/generators/partial.py +119 -0
- annet/generators/perf.py +79 -0
- annet/generators/ref.py +15 -0
- annet/generators/result.py +127 -0
- annet/hardware.py +45 -0
- annet/implicit.py +139 -0
- annet/lib.py +128 -0
- annet/output.py +167 -0
- annet/parallel.py +448 -0
- annet/patching.py +25 -0
- annet/reference.py +148 -0
- annet/rulebook/__init__.py +114 -0
- annet/rulebook/arista/__init__.py +0 -0
- annet/rulebook/arista/iface.py +16 -0
- annet/rulebook/aruba/__init__.py +16 -0
- annet/rulebook/aruba/ap_env.py +146 -0
- annet/rulebook/aruba/misc.py +8 -0
- annet/rulebook/cisco/__init__.py +0 -0
- annet/rulebook/cisco/iface.py +68 -0
- annet/rulebook/cisco/misc.py +57 -0
- annet/rulebook/cisco/vlandb.py +90 -0
- annet/rulebook/common.py +19 -0
- annet/rulebook/deploying.py +87 -0
- annet/rulebook/huawei/__init__.py +0 -0
- annet/rulebook/huawei/aaa.py +75 -0
- annet/rulebook/huawei/bgp.py +97 -0
- annet/rulebook/huawei/iface.py +33 -0
- annet/rulebook/huawei/misc.py +337 -0
- annet/rulebook/huawei/vlandb.py +115 -0
- annet/rulebook/juniper/__init__.py +107 -0
- annet/rulebook/nexus/__init__.py +0 -0
- annet/rulebook/nexus/iface.py +92 -0
- annet/rulebook/patching.py +143 -0
- annet/rulebook/ribbon/__init__.py +12 -0
- annet/rulebook/texts/arista.deploy +20 -0
- annet/rulebook/texts/arista.order +125 -0
- annet/rulebook/texts/arista.rul +59 -0
- annet/rulebook/texts/aruba.deploy +20 -0
- annet/rulebook/texts/aruba.order +83 -0
- annet/rulebook/texts/aruba.rul +87 -0
- annet/rulebook/texts/cisco.deploy +27 -0
- annet/rulebook/texts/cisco.order +82 -0
- annet/rulebook/texts/cisco.rul +105 -0
- annet/rulebook/texts/huawei.deploy +188 -0
- annet/rulebook/texts/huawei.order +388 -0
- annet/rulebook/texts/huawei.rul +471 -0
- annet/rulebook/texts/juniper.rul +120 -0
- annet/rulebook/texts/nexus.deploy +24 -0
- annet/rulebook/texts/nexus.order +85 -0
- annet/rulebook/texts/nexus.rul +83 -0
- annet/rulebook/texts/nokia.rul +31 -0
- annet/rulebook/texts/pc.order +5 -0
- annet/rulebook/texts/pc.rul +9 -0
- annet/rulebook/texts/ribbon.deploy +22 -0
- annet/rulebook/texts/ribbon.rul +77 -0
- annet/rulebook/texts/routeros.order +38 -0
- annet/rulebook/texts/routeros.rul +45 -0
- annet/storage.py +125 -0
- annet/tabparser.py +36 -0
- annet/text_term_format.py +95 -0
- annet/tracing.py +170 -0
- annet/types.py +227 -0
- annet-0.0.dist-info/AUTHORS +21 -0
- annet-0.0.dist-info/LICENSE +21 -0
- annet-0.0.dist-info/METADATA +26 -0
- annet-0.0.dist-info/RECORD +137 -0
- annet-0.0.dist-info/WHEEL +5 -0
- annet-0.0.dist-info/entry_points.txt +5 -0
- annet-0.0.dist-info/top_level.txt +2 -0
- annet_generators/__init__.py +0 -0
- annet_generators/example/__init__.py +12 -0
- annet_generators/example/lldp.py +53 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import re
|
|
3
|
+
from collections import OrderedDict as odict
|
|
4
|
+
|
|
5
|
+
from .types import Op
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# =====
|
|
9
|
+
class ParserError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# =====
|
|
14
|
+
class _CommentOrEmpty:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BlockBegin:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BlockEnd:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# =====
|
|
27
|
+
class CommonFormatter:
|
|
28
|
+
def __init__(self, indent=" "):
|
|
29
|
+
self._indent = indent
|
|
30
|
+
self._block_begin = ""
|
|
31
|
+
self._block_end = ""
|
|
32
|
+
self._statement_end = ""
|
|
33
|
+
|
|
34
|
+
def split(self, text):
|
|
35
|
+
return list(filter(None, text.split("\n")))
|
|
36
|
+
|
|
37
|
+
def join(self, config):
|
|
38
|
+
return "\n".join(
|
|
39
|
+
_filtered_block_marks(
|
|
40
|
+
self._indent_blocks(self._blocks(config, is_patch=False))
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def diff(self, diff):
|
|
45
|
+
return list(self._diff_lines(diff))
|
|
46
|
+
|
|
47
|
+
def diff_generator(self, diff):
|
|
48
|
+
yield from self._diff_lines(diff)
|
|
49
|
+
|
|
50
|
+
def patch(self, patch):
|
|
51
|
+
return "\n".join(
|
|
52
|
+
_filtered_block_marks(
|
|
53
|
+
self._indent_blocks(self._blocks(patch, is_patch=True))
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def cmd_paths(self, patch):
|
|
58
|
+
ret = odict()
|
|
59
|
+
path = []
|
|
60
|
+
for row, context in self.blocks_and_context(patch, is_patch=True):
|
|
61
|
+
if row is BlockBegin:
|
|
62
|
+
path.append(path[-1])
|
|
63
|
+
elif row is BlockEnd:
|
|
64
|
+
path.pop()
|
|
65
|
+
else:
|
|
66
|
+
if path:
|
|
67
|
+
path.pop()
|
|
68
|
+
path.append(row)
|
|
69
|
+
ret[tuple(path)] = context
|
|
70
|
+
return ret
|
|
71
|
+
|
|
72
|
+
def patch_plain(self, patch):
|
|
73
|
+
return list(self.cmd_paths(patch).keys())
|
|
74
|
+
|
|
75
|
+
def _diff_lines(self, diff, _level=0, _block_sign=None):
|
|
76
|
+
sign_map = {
|
|
77
|
+
Op.REMOVED: "-",
|
|
78
|
+
Op.ADDED: "+",
|
|
79
|
+
Op.MOVED: ">",
|
|
80
|
+
Op.AFFECTED: " ",
|
|
81
|
+
}
|
|
82
|
+
for (flag, row, children, _) in diff:
|
|
83
|
+
sign = sign_map[flag]
|
|
84
|
+
if not children:
|
|
85
|
+
yield "%s %s%s" % (sign, self._indent * _level, row + self._statement_end)
|
|
86
|
+
else:
|
|
87
|
+
yield "%s %s%s" % (sign, self._indent * _level, row + self._block_begin)
|
|
88
|
+
yield from self._diff_lines(children, _level + 1, sign)
|
|
89
|
+
if _level > 0 and self._block_end and _block_sign is not None:
|
|
90
|
+
yield "%s %s%s" % (_block_sign, self._indent * (_level - 1), self._block_end)
|
|
91
|
+
|
|
92
|
+
def _indented_blocks(self, tree):
|
|
93
|
+
return self._indent_blocks(self._blocks(tree, False))
|
|
94
|
+
|
|
95
|
+
def _indent_blocks(self, blocks):
|
|
96
|
+
_level = 0
|
|
97
|
+
for row in blocks:
|
|
98
|
+
if row is BlockBegin:
|
|
99
|
+
_level += 1
|
|
100
|
+
elif row is BlockEnd:
|
|
101
|
+
_level -= 1
|
|
102
|
+
else:
|
|
103
|
+
row = self._indent * _level + row
|
|
104
|
+
yield row
|
|
105
|
+
|
|
106
|
+
def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
|
|
107
|
+
empty_context = {}
|
|
108
|
+
if is_patch:
|
|
109
|
+
items = ((item.row, item.child, item.context) for item in tree.itms)
|
|
110
|
+
else:
|
|
111
|
+
items = ((row, child, empty_context) for row, child in tree.items())
|
|
112
|
+
for row, sub_config, context in items:
|
|
113
|
+
yield row, context
|
|
114
|
+
if sub_config or (is_patch and sub_config is not None):
|
|
115
|
+
yield BlockBegin, None
|
|
116
|
+
yield from self.blocks_and_context(sub_config, is_patch, _level + 1, row)
|
|
117
|
+
yield BlockEnd, None
|
|
118
|
+
|
|
119
|
+
def _blocks(self, tree, is_patch):
|
|
120
|
+
for row, _context in self.blocks_and_context(tree, is_patch):
|
|
121
|
+
yield row
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class BlockExitFormatter(CommonFormatter):
|
|
125
|
+
def __init__(self, block_exit, no_block_exit=(), indent=" "):
|
|
126
|
+
super().__init__(indent)
|
|
127
|
+
self._block_exit = block_exit
|
|
128
|
+
self._no_block_exit = tuple(no_block_exit)
|
|
129
|
+
|
|
130
|
+
def split_remove_spaces(self, text):
|
|
131
|
+
# эта регулярка заменяет 2 и более пробела на один, но оставляет пробелы в начале линии
|
|
132
|
+
text = re.sub(r"(?<=\S)\ {2,}(?=\S)", " ", text)
|
|
133
|
+
res = super().split(text)
|
|
134
|
+
return res
|
|
135
|
+
|
|
136
|
+
def block_exit(self, parent):
|
|
137
|
+
if not (parent and parent.startswith(self._no_block_exit)):
|
|
138
|
+
return self._block_exit
|
|
139
|
+
|
|
140
|
+
def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
|
|
141
|
+
last_context = {}
|
|
142
|
+
for row, context in super().blocks_and_context(tree, is_patch, _level, _parent):
|
|
143
|
+
yield row, context
|
|
144
|
+
if context is not None:
|
|
145
|
+
last_context = context
|
|
146
|
+
if is_patch and _level > 0:
|
|
147
|
+
exit_statement = self.block_exit(_parent)
|
|
148
|
+
if exit_statement:
|
|
149
|
+
yield exit_statement, last_context
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class HuaweiFormatter(BlockExitFormatter):
|
|
153
|
+
def __init__(self, indent=" "):
|
|
154
|
+
super().__init__(
|
|
155
|
+
block_exit="quit",
|
|
156
|
+
no_block_exit=[
|
|
157
|
+
"rsa peer-public-key",
|
|
158
|
+
"dsa peer-public-key",
|
|
159
|
+
"public-key-code begin",
|
|
160
|
+
],
|
|
161
|
+
indent=indent,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def split(self, text):
|
|
165
|
+
# на старых прошивка наблюдается баг с двумя пробелами в этом месте в конфиге
|
|
166
|
+
# например на VRP V100R006C00SPC500 + V100R006SPH003
|
|
167
|
+
policy_end_blocks = ("end-list", "endif", "end-filter")
|
|
168
|
+
tree = self.split_remove_spaces(text)
|
|
169
|
+
tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
|
|
170
|
+
return tree
|
|
171
|
+
|
|
172
|
+
def block_exit(self, parent):
|
|
173
|
+
if parent:
|
|
174
|
+
if parent.startswith("xpl route-filter"):
|
|
175
|
+
return "end-filter"
|
|
176
|
+
elif parent.startswith(("xpl")):
|
|
177
|
+
return "end-list"
|
|
178
|
+
elif parent.startswith("if") and parent.endswith("then"):
|
|
179
|
+
return "endif"
|
|
180
|
+
return super().block_exit(parent)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class CiscoFormatter(BlockExitFormatter):
|
|
184
|
+
def __init__(self, indent=" "):
|
|
185
|
+
super().__init__("exit", indent)
|
|
186
|
+
|
|
187
|
+
def split(self, text):
|
|
188
|
+
return self.split_remove_spaces(text)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AsrFormatter(BlockExitFormatter):
|
|
192
|
+
def __init__(self, indent=" "):
|
|
193
|
+
super().__init__("exit", indent)
|
|
194
|
+
|
|
195
|
+
def split(self, text):
|
|
196
|
+
policy_end_blocks = ("end-set", "endif", "end-policy")
|
|
197
|
+
tree = self.split_remove_spaces(text)
|
|
198
|
+
tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
|
|
199
|
+
return tree
|
|
200
|
+
|
|
201
|
+
def block_exit(self, parent):
|
|
202
|
+
if parent:
|
|
203
|
+
if parent.startswith(("prefix-set", "as-path-set", "community-set")):
|
|
204
|
+
return "end-set"
|
|
205
|
+
elif parent.startswith("if") and parent.endswith("then"):
|
|
206
|
+
return "endif"
|
|
207
|
+
elif parent.startswith("route-policy"):
|
|
208
|
+
return "end-policy"
|
|
209
|
+
return super().block_exit(parent)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class JuniperFormatter(CommonFormatter):
|
|
213
|
+
patch_set_prefix = "set "
|
|
214
|
+
|
|
215
|
+
def __init__(self, indent=" "):
|
|
216
|
+
super().__init__(indent)
|
|
217
|
+
self._block_begin = " {"
|
|
218
|
+
self._block_end = "}"
|
|
219
|
+
self._statement_end = ";"
|
|
220
|
+
self._endofline_comment = "; ##"
|
|
221
|
+
|
|
222
|
+
def split(self, text):
|
|
223
|
+
sub_regexs = (
|
|
224
|
+
(re.compile(self._block_begin + r"\s*" + self._block_end + r"$"), ""), # collapse empty blocks
|
|
225
|
+
(re.compile(self._block_begin + "(\t# .+)?$"), ""),
|
|
226
|
+
(re.compile(self._statement_end + r"$"), ""),
|
|
227
|
+
(re.compile(r"\s*" + self._block_end + "(\t# .+)?$"), ""),
|
|
228
|
+
(re.compile(self._endofline_comment + r".*$"), ""),
|
|
229
|
+
)
|
|
230
|
+
split = []
|
|
231
|
+
for line in text.split("\n"):
|
|
232
|
+
for (regex, repl_line) in sub_regexs:
|
|
233
|
+
line = regex.sub(repl_line, line)
|
|
234
|
+
split.append(line)
|
|
235
|
+
return list(filter(None, split))
|
|
236
|
+
|
|
237
|
+
def join(self, config):
|
|
238
|
+
return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
|
|
239
|
+
|
|
240
|
+
def patch(self, patch):
|
|
241
|
+
return "\n".join(" ".join(x) for x in self.cmd_paths(patch))
|
|
242
|
+
|
|
243
|
+
def patch_plain(self, patch):
|
|
244
|
+
return list(self.cmd_paths(patch).keys())
|
|
245
|
+
|
|
246
|
+
def _formatted_blocks(self, blocks):
|
|
247
|
+
level = 0
|
|
248
|
+
line = None
|
|
249
|
+
for new_line in blocks:
|
|
250
|
+
if new_line is BlockBegin:
|
|
251
|
+
level += 1
|
|
252
|
+
if isinstance(line, str):
|
|
253
|
+
yield line + self._block_begin
|
|
254
|
+
elif new_line is BlockEnd:
|
|
255
|
+
level -= 1
|
|
256
|
+
if isinstance(line, str):
|
|
257
|
+
yield line + self._statement_end
|
|
258
|
+
yield self._indent * level + self._block_end
|
|
259
|
+
elif isinstance(line, str):
|
|
260
|
+
yield line + self._statement_end
|
|
261
|
+
line = new_line
|
|
262
|
+
if isinstance(line, str):
|
|
263
|
+
yield line + self._statement_end
|
|
264
|
+
|
|
265
|
+
def cmd_paths(self, patch, _prev=""):
|
|
266
|
+
commands = odict()
|
|
267
|
+
for item in patch.itms:
|
|
268
|
+
key, childs, context = item.row, item.child, item.context
|
|
269
|
+
if childs:
|
|
270
|
+
for k, v in self.cmd_paths(childs, _prev + " " + key).items():
|
|
271
|
+
commands[k] = v
|
|
272
|
+
else:
|
|
273
|
+
if key.startswith("delete"):
|
|
274
|
+
cmd = "delete" + _prev + " " + key.replace("delete", "", 1).strip()
|
|
275
|
+
elif key.startswith("activate"):
|
|
276
|
+
cmd = "activate" + _prev + " " + key.replace("activate", "", 1).strip()
|
|
277
|
+
elif key.startswith("deactivate"):
|
|
278
|
+
cmd = "deactivate" + _prev + " " + key.replace("deactivate", "", 1).strip()
|
|
279
|
+
else:
|
|
280
|
+
cmd = (self.patch_set_prefix + _prev.strip()).strip() + " " + key
|
|
281
|
+
# Expanding [ a b c ] junipers list of arguments
|
|
282
|
+
matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
|
|
283
|
+
if matches:
|
|
284
|
+
for c in matches.group(2).split(" "):
|
|
285
|
+
if c.strip():
|
|
286
|
+
cmd = " ".join([matches.group(1), c])
|
|
287
|
+
commands[(cmd,)] = context
|
|
288
|
+
else:
|
|
289
|
+
commands[(cmd,)] = context
|
|
290
|
+
|
|
291
|
+
return commands
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class RibbonFormatter(JuniperFormatter):
|
|
295
|
+
def __init__(self, *args, **kwargs):
|
|
296
|
+
super().__init__(*args, **kwargs)
|
|
297
|
+
self._endofline_comment = "; # SECRET-DATA"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class JuniperList:
|
|
301
|
+
"""
|
|
302
|
+
Форматирует inline-листы в конфиге juniper
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
def __init__(self, *args, spaces=True, **kwargs):
|
|
306
|
+
self._items = list(*args, **kwargs)
|
|
307
|
+
self.spaces = spaces
|
|
308
|
+
|
|
309
|
+
def __str__(self):
|
|
310
|
+
if self.spaces:
|
|
311
|
+
return "[ %s ]" % " ".join(str(_) for _ in self._items)
|
|
312
|
+
else:
|
|
313
|
+
return "[%s]" % " ".join(str(_) for _ in self._items)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class NokiaFormatter(JuniperFormatter):
|
|
317
|
+
patch_set_prefix = "/configure "
|
|
318
|
+
|
|
319
|
+
def __init__(self, *args, **kwargs):
|
|
320
|
+
super().__init__(*args, **kwargs)
|
|
321
|
+
self._statement_end = ""
|
|
322
|
+
self._endofline_comment = " ##"
|
|
323
|
+
|
|
324
|
+
def split(self, text):
|
|
325
|
+
ret = super().split(text)
|
|
326
|
+
# NOCDEVDUTY-248 сдергиваем верхний configure-блок
|
|
327
|
+
# NOCDEVDUTY-282 после configure {} блока могут идти еще блоки которые нам не нужны
|
|
328
|
+
start, finish = None, None
|
|
329
|
+
for i, line in enumerate(ret):
|
|
330
|
+
if line.startswith("#"):
|
|
331
|
+
continue
|
|
332
|
+
# начало configure-блока
|
|
333
|
+
if line == "configure":
|
|
334
|
+
start = i + 1
|
|
335
|
+
# любой после configure последующий блок на глобальном уровне
|
|
336
|
+
elif len(line) == len(line.lstrip()):
|
|
337
|
+
if start is not None and finish is None:
|
|
338
|
+
finish = i
|
|
339
|
+
# Если configure-блока не было - то весь конфиг считаем configre'ом
|
|
340
|
+
start = start if start is not None else 0
|
|
341
|
+
finish = finish if finish is not None else len(ret)
|
|
342
|
+
return ret[start:finish]
|
|
343
|
+
|
|
344
|
+
def cmd_paths(self, patch, _prev=""):
|
|
345
|
+
commands = odict()
|
|
346
|
+
for item in patch.itms:
|
|
347
|
+
key, childs, context = item.row, item.child, item.context
|
|
348
|
+
if childs:
|
|
349
|
+
for k, v in self.cmd_paths(childs, _prev + " " + key).items():
|
|
350
|
+
commands[k] = v
|
|
351
|
+
else:
|
|
352
|
+
if key.startswith("delete"):
|
|
353
|
+
cmd = "/configure delete" + _prev + " " + key.replace("delete", "", 1).strip()
|
|
354
|
+
else:
|
|
355
|
+
cmd = self.patch_set_prefix + _prev.strip() + " " + key
|
|
356
|
+
# Expanding [ a b c ] junipers list of arguments
|
|
357
|
+
matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
|
|
358
|
+
if matches:
|
|
359
|
+
for c in matches.group(2).split(" "):
|
|
360
|
+
if c.strip():
|
|
361
|
+
cmd = " ".join([matches.group(1), c])
|
|
362
|
+
commands[(cmd,)] = context
|
|
363
|
+
else:
|
|
364
|
+
commands[(cmd,)] = context
|
|
365
|
+
return commands
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class RosFormatter(CommonFormatter):
|
|
369
|
+
patch_set_prefix = "set "
|
|
370
|
+
|
|
371
|
+
def __init__(self, *args, **kwargs):
|
|
372
|
+
super().__init__(*args, **kwargs)
|
|
373
|
+
self._block_begin = "/"
|
|
374
|
+
|
|
375
|
+
def join(self, config):
|
|
376
|
+
return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
|
|
377
|
+
|
|
378
|
+
def patch(self, patch):
|
|
379
|
+
return "\n".join(" ".join(x) for x in self.cmd_paths(patch))
|
|
380
|
+
|
|
381
|
+
def patch_plain(self, patch):
|
|
382
|
+
return list(self.cmd_paths(patch).keys())
|
|
383
|
+
|
|
384
|
+
def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
|
|
385
|
+
rows = []
|
|
386
|
+
empty_context = {}
|
|
387
|
+
if is_patch:
|
|
388
|
+
items = ((item.row, item.child, item.context) for item in tree.itms)
|
|
389
|
+
else:
|
|
390
|
+
items = ((row, child, empty_context) for row, child in tree.items())
|
|
391
|
+
for row, sub_config, context in items:
|
|
392
|
+
if sub_config or (is_patch and sub_config is not None):
|
|
393
|
+
rows.append((row, sub_config, context))
|
|
394
|
+
else:
|
|
395
|
+
rows.append((row, None, context))
|
|
396
|
+
|
|
397
|
+
prev_prow = None
|
|
398
|
+
for sub_config, row_group in itertools.groupby(rows, lambda x: x[1]):
|
|
399
|
+
if sub_config is None:
|
|
400
|
+
if prev_prow:
|
|
401
|
+
yield prev_prow
|
|
402
|
+
yield BlockBegin, None
|
|
403
|
+
for (row, _, context) in row_group:
|
|
404
|
+
yield row, context
|
|
405
|
+
if prev_prow:
|
|
406
|
+
yield BlockEnd, None
|
|
407
|
+
else:
|
|
408
|
+
for (row, _, context) in row_group:
|
|
409
|
+
if _parent:
|
|
410
|
+
prev_prow = _parent
|
|
411
|
+
prow = f"{_parent} {row}"
|
|
412
|
+
else:
|
|
413
|
+
prow = row
|
|
414
|
+
yield prow, context
|
|
415
|
+
yield BlockBegin, None
|
|
416
|
+
yield from self.blocks_and_context(sub_config, is_patch, _level + 1, prow)
|
|
417
|
+
yield BlockEnd, None
|
|
418
|
+
|
|
419
|
+
def _formatted_blocks(self, blocks):
|
|
420
|
+
line = None
|
|
421
|
+
for new_line in blocks:
|
|
422
|
+
if new_line is BlockBegin:
|
|
423
|
+
if isinstance(line, str):
|
|
424
|
+
yield self._block_begin + line.strip()
|
|
425
|
+
elif isinstance(line, str):
|
|
426
|
+
yield line
|
|
427
|
+
line = new_line
|
|
428
|
+
|
|
429
|
+
def _splitter_file(self, lines):
|
|
430
|
+
filedesrc_re = re.compile(r"^\s+(?P<num>\d+)\s+name=\"(?P<name>[^\"]+)\"\s+type=\"(?P<type>[^\"]+)\""
|
|
431
|
+
r"\s+(size=(?P<size>.*))?creation-time=(?P<time>.*?)(contents=(?P<content>.*)?)?$")
|
|
432
|
+
file_content_indent = re.compile(r"^\s{5}")
|
|
433
|
+
out = []
|
|
434
|
+
files = {}
|
|
435
|
+
curfile = None
|
|
436
|
+
for line in lines:
|
|
437
|
+
match = filedesrc_re.search(line)
|
|
438
|
+
if match:
|
|
439
|
+
if match.group("type").strip() == ".txt file":
|
|
440
|
+
curfile = match.group("name")
|
|
441
|
+
files[curfile] = {"name": curfile, "contents": []}
|
|
442
|
+
if match.group("content"):
|
|
443
|
+
files[curfile]["contents"].append(match.group("content").strip())
|
|
444
|
+
elif curfile and file_content_indent.match(line):
|
|
445
|
+
files[curfile]["contents"].append(file_content_indent.sub("", line))
|
|
446
|
+
for file in files.values():
|
|
447
|
+
out.append(f"print file={file['name']}")
|
|
448
|
+
if len(file["contents"]) > 0:
|
|
449
|
+
text = "\n".join(file["contents"])
|
|
450
|
+
out.append(f"set {file['name']} contents=\"{text}\"")
|
|
451
|
+
return out
|
|
452
|
+
|
|
453
|
+
def _splitter_user_ssh_keys(self, lines):
|
|
454
|
+
keydescr_re = re.compile(r"user=(?P<user>\w+).*key-owner=(?P<owner>.*)$")
|
|
455
|
+
out = []
|
|
456
|
+
for line in lines:
|
|
457
|
+
match = keydescr_re.search(line)
|
|
458
|
+
if match:
|
|
459
|
+
out.append(f"import public-key-file={match.group('owner')}.ssh_key.txt user={match.group('user')}")
|
|
460
|
+
|
|
461
|
+
return out
|
|
462
|
+
|
|
463
|
+
def split(self, text):
|
|
464
|
+
split = []
|
|
465
|
+
level = 0
|
|
466
|
+
postj = {}
|
|
467
|
+
curgroup = None
|
|
468
|
+
for line in text.split("\n"):
|
|
469
|
+
if line.startswith("/"):
|
|
470
|
+
if curgroup:
|
|
471
|
+
for row in getattr(self, curgroup)(postj[curgroup]):
|
|
472
|
+
if level > 0:
|
|
473
|
+
row = row.strip()
|
|
474
|
+
split.append(self._indent * level + row)
|
|
475
|
+
|
|
476
|
+
level = 0
|
|
477
|
+
for group in line.split():
|
|
478
|
+
split.append(self._indent * level + group.replace("/", ""))
|
|
479
|
+
level += 1
|
|
480
|
+
|
|
481
|
+
gpath = line.replace("/", "_splitter_").replace(" ", "_").replace("-", "_")
|
|
482
|
+
if hasattr(self, gpath):
|
|
483
|
+
postj[gpath] = []
|
|
484
|
+
curgroup = gpath
|
|
485
|
+
else:
|
|
486
|
+
curgroup = None
|
|
487
|
+
else:
|
|
488
|
+
row = line
|
|
489
|
+
if curgroup:
|
|
490
|
+
postj[curgroup].append(row)
|
|
491
|
+
else:
|
|
492
|
+
if level > 0:
|
|
493
|
+
row = line.strip()
|
|
494
|
+
split.append(self._indent * level + row)
|
|
495
|
+
if curgroup:
|
|
496
|
+
for row in getattr(self, curgroup)(postj[curgroup]):
|
|
497
|
+
if level > 0:
|
|
498
|
+
row = row.strip()
|
|
499
|
+
split.append(self._indent * level + row)
|
|
500
|
+
return list(filter(None, split))
|
|
501
|
+
|
|
502
|
+
def cmd_paths(self, patch, _prev=""):
|
|
503
|
+
rm_regexs = (
|
|
504
|
+
(re.compile(r"^add "), ""),
|
|
505
|
+
(re.compile(r"^print file="), "name="),
|
|
506
|
+
)
|
|
507
|
+
patch_items = []
|
|
508
|
+
for item in patch.itms:
|
|
509
|
+
key, childs, context = item.row, item.child, item.context
|
|
510
|
+
if childs:
|
|
511
|
+
patch_items.append((key, childs, context))
|
|
512
|
+
else:
|
|
513
|
+
patch_items.append((key, None, context))
|
|
514
|
+
|
|
515
|
+
commands = odict()
|
|
516
|
+
prev_cmd = None
|
|
517
|
+
prev_context = None
|
|
518
|
+
for childs, items in itertools.groupby(patch_items, lambda x: x[1]):
|
|
519
|
+
if childs is None:
|
|
520
|
+
if prev_cmd:
|
|
521
|
+
commands[(prev_cmd,)] = prev_context
|
|
522
|
+
for key, _, context in items:
|
|
523
|
+
if key.startswith("remove"):
|
|
524
|
+
find_cmd = key.replace("remove", "", 1).strip()
|
|
525
|
+
for (regex, repl_line) in rm_regexs:
|
|
526
|
+
find_cmd = regex.sub(repl_line, find_cmd)
|
|
527
|
+
cmd = "remove [ find " + find_cmd + " ]"
|
|
528
|
+
else:
|
|
529
|
+
cmd = key
|
|
530
|
+
commands[(cmd,)] = context
|
|
531
|
+
else:
|
|
532
|
+
for key, _, context in items:
|
|
533
|
+
if _prev:
|
|
534
|
+
prev_cmd = _prev
|
|
535
|
+
prev_context = context
|
|
536
|
+
block_cmd = f"{_prev} {key}"
|
|
537
|
+
else:
|
|
538
|
+
block_cmd = f"/{key}"
|
|
539
|
+
commands[(block_cmd,)] = context
|
|
540
|
+
for k, v in self.cmd_paths(childs, block_cmd).items():
|
|
541
|
+
commands[k] = v
|
|
542
|
+
return commands
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def make_formatter(vendor, **kwargs):
|
|
546
|
+
formatters = {
|
|
547
|
+
"juniper": JuniperFormatter,
|
|
548
|
+
"cisco": CiscoFormatter,
|
|
549
|
+
"nexus": CiscoFormatter,
|
|
550
|
+
"huawei": HuaweiFormatter,
|
|
551
|
+
"arista": CiscoFormatter,
|
|
552
|
+
"nokia": NokiaFormatter,
|
|
553
|
+
"routeros": RosFormatter,
|
|
554
|
+
"aruba": CiscoFormatter,
|
|
555
|
+
"pc": CommonFormatter,
|
|
556
|
+
"ribbon": RibbonFormatter,
|
|
557
|
+
}
|
|
558
|
+
return formatters[vendor](**kwargs)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# ====
|
|
562
|
+
def parse_to_tree(text, splitter, comments=("!", "#")):
|
|
563
|
+
tree = odict()
|
|
564
|
+
for stack in _stacked(splitter(text), tuple(comments)):
|
|
565
|
+
local_tree = tree
|
|
566
|
+
for key in stack:
|
|
567
|
+
if key not in local_tree:
|
|
568
|
+
local_tree[key] = odict()
|
|
569
|
+
local_tree = local_tree[key]
|
|
570
|
+
return tree
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# =====
|
|
574
|
+
def _stacked(lines, comments):
|
|
575
|
+
stack = []
|
|
576
|
+
for (level, line) in _stripped_indents(lines, comments):
|
|
577
|
+
level += 1
|
|
578
|
+
if level > len(stack):
|
|
579
|
+
stack.append(line)
|
|
580
|
+
elif level == len(stack):
|
|
581
|
+
stack[-1] = line
|
|
582
|
+
else:
|
|
583
|
+
stack = stack[:level - 1] + [line]
|
|
584
|
+
yield tuple(stack)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _stripped_indents(lines, comments):
|
|
588
|
+
indents = []
|
|
589
|
+
curr_level = 0
|
|
590
|
+
g_level = None
|
|
591
|
+
|
|
592
|
+
for (number, (level, line)) in enumerate(_parsed_indents(lines, comments), start=1):
|
|
593
|
+
if isinstance(line, str):
|
|
594
|
+
if g_level is None:
|
|
595
|
+
g_level = level
|
|
596
|
+
level = level - (g_level or 0)
|
|
597
|
+
if level < 0:
|
|
598
|
+
raise ParserError("Invalid top indention: line %d: %s" % (number, line))
|
|
599
|
+
|
|
600
|
+
if level > curr_level:
|
|
601
|
+
indents.append(level - curr_level)
|
|
602
|
+
curr_level += level - curr_level
|
|
603
|
+
elif level < curr_level:
|
|
604
|
+
while curr_level > level and len(indents):
|
|
605
|
+
curr_level -= indents.pop()
|
|
606
|
+
if curr_level != level:
|
|
607
|
+
raise ParserError("Invalid top indention: line %d: %s" % (number, line))
|
|
608
|
+
|
|
609
|
+
yield (len(indents), line)
|
|
610
|
+
|
|
611
|
+
elif line is BlockEnd:
|
|
612
|
+
indents = []
|
|
613
|
+
curr_level = 0
|
|
614
|
+
g_level = None
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _parsed_indents(lines, comments):
|
|
618
|
+
for line in _filtered_lines(lines, comments):
|
|
619
|
+
if isinstance(line, str):
|
|
620
|
+
yield (_parse_indent(line), line.strip())
|
|
621
|
+
else:
|
|
622
|
+
yield (0, line)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _filtered_lines(lines, comments):
|
|
626
|
+
for line in lines:
|
|
627
|
+
stripped = line.strip()
|
|
628
|
+
# TODO Это для хуавей, так что хелпер нужно унести в Formatter
|
|
629
|
+
if "#" in comments and line.startswith("#"):
|
|
630
|
+
yield BlockEnd
|
|
631
|
+
elif len(stripped) == 0 or stripped.startswith(comments):
|
|
632
|
+
yield _CommentOrEmpty
|
|
633
|
+
else:
|
|
634
|
+
yield line
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _filtered_block_marks(blocks):
|
|
638
|
+
return filter(lambda b: isinstance(b, str), blocks)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _parse_indent(line):
|
|
642
|
+
level = 0
|
|
643
|
+
for ch in line:
|
|
644
|
+
if ch in ("\t", " "):
|
|
645
|
+
level += 1
|
|
646
|
+
else:
|
|
647
|
+
break
|
|
648
|
+
return level
|
annet/annlib/types.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import List, Tuple
|
|
3
|
+
|
|
4
|
+
Diff = List[Tuple]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Операции отмечающие роль команды в дифе
|
|
8
|
+
# XXX надо отдельно переделать на enum
|
|
9
|
+
class Op:
|
|
10
|
+
ADDED = "added"
|
|
11
|
+
REMOVED = "removed"
|
|
12
|
+
AFFECTED = "affected"
|
|
13
|
+
MOVED = "moved"
|
|
14
|
+
UNCHANGED = "unchanged"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GeneratorType(enum.Enum):
|
|
18
|
+
PARTIAL = "partial"
|
|
19
|
+
ENTIRE = "entire"
|
|
20
|
+
JSON_FRAGMENT = "json_fragment"
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def fromstring(value: str) -> "GeneratorType":
|
|
24
|
+
return GeneratorType(value)
|
|
25
|
+
|
|
26
|
+
def tostring(self) -> str:
|
|
27
|
+
return self.value
|
|
28
|
+
|
|
29
|
+
def __lt__(self, other: "GeneratorType") -> bool:
|
|
30
|
+
return self.value < other.value
|
|
31
|
+
|
|
32
|
+
def __le__(self, other: "GeneratorType") -> bool:
|
|
33
|
+
if self != other:
|
|
34
|
+
return self.value < other.value
|
|
35
|
+
return True
|