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,196 @@
1
+ import collections
2
+ import re
3
+ import typing
4
+
5
+ from . import patching, tabparser
6
+ from .diff import diff_ops, ops_sign
7
+ from .rbparser import acl
8
+
9
+ UnifiedInputConfig = str # Конфиг классических сетевых устройств
10
+ FileInputConfig = typing.Dict[str, typing.Any] # Конфиг вайтбоксов и серверов
11
+ InputConfig = typing.Union[UnifiedInputConfig, FileInputConfig]
12
+
13
+ Acl = typing.Dict[str, typing.Any]
14
+
15
+ UnifiedConfigTree = typing.OrderedDict[str, typing.Any]
16
+ FileConfigTree = typing.Dict[str, typing.Any]
17
+ ConfigTree = typing.Union[UnifiedConfigTree, FileConfigTree]
18
+
19
+ DiffTree = typing.OrderedDict[str, typing.Any]
20
+ UnifiedDiff = typing.List[typing.Tuple[str, str, typing.List, typing.Optional[int]]]
21
+ FileConfigDiff = typing.Dict[str, typing.Any]
22
+ Diff = typing.Union[UnifiedDiff, FileConfigDiff]
23
+
24
+
25
+ def make_acl(text: str, vendor: str) -> Acl:
26
+ return acl.compile_acl_text(text, vendor)
27
+
28
+
29
+ def filter_config(acl: Acl, fmtr: tabparser.CommonFormatter, input_config: InputConfig) -> InputConfig:
30
+ if isinstance(input_config, str):
31
+ config: ConfigTree = tabparser.parse_to_tree(input_config, fmtr.split)
32
+ config = patching.apply_acl(config, acl, fatal_acl=False)
33
+ config = fmtr.join(config)
34
+ else:
35
+ config = typing.cast(input_config, FileConfigTree)
36
+ config = apply_acl_fileconfig(input_config, acl)
37
+ return config
38
+
39
+
40
+ def filter_diff(acl: Acl, fmtr: tabparser.CommonFormatter, input_config: InputConfig) -> InputConfig:
41
+ if isinstance(input_config, str):
42
+ input_config = shift_op(input_config)
43
+ diff_tee: DiffTree = tabparser.parse_to_tree(input_config, fmtr.split)
44
+ diff: Diff = tree_to_diff(diff_tee)
45
+ diff = patching.apply_acl_diff(diff, acl)
46
+ config = fmtr.join(diff_to_tree(diff))
47
+ config = unshift_op(config)
48
+ config = config.rstrip()
49
+ else:
50
+ config = typing.cast(input_config, FileConfigTree)
51
+ config = apply_acl_fileconfig(input_config, acl)
52
+ return config
53
+
54
+
55
+ def filter_patch(acl: Acl, fmtr: tabparser.CommonFormatter, text: str) -> str:
56
+ return filter_config(acl, fmtr, text)
57
+
58
+
59
+ # NOCDEV-6378 на патч для Juniper/Nokia нельзя просто так наложить filter_acl
60
+ def filter_patch_jun_nokia(diff_filtered: InputConfig, fmtr: tabparser.CommonFormatter, text: str) -> str:
61
+ """
62
+ Накладываем ACL на патчи для Juniper/Nokia
63
+
64
+ Поскольку в патче уже потерена иерархия команд - они развернуты в строки типа
65
+ Нужна дополнительная информация о изначальном конфиге, которую можно подсмотреть в дифе
66
+ set interface et-0/0/0 unit ....
67
+ delete interface et-0/0/0 unit ....
68
+ /configure port 1/1/c17/1 ...
69
+ /configure delete port 1/1/c18/1 ...
70
+ """
71
+ diff_tree_stripped: DiffTree = tabparser.parse_to_tree(strip_op(diff_filtered), fmtr.split)
72
+ _tree_expand_lists_nokia_jun(diff_tree_stripped)
73
+ patch_lines_passed = []
74
+ for patch_line in text.split("\n"):
75
+ patch_parts = [x for x in patch_line.split(" ") if x]
76
+ diff_current = diff_tree_stripped
77
+ # strip set|delete|/configure
78
+ while patch_parts and patch_parts[0] in {"/configure", "set", "delete"}:
79
+ patch_parts = patch_parts[1:]
80
+ while patch_parts and diff_current:
81
+ for i in range(len(patch_parts), -1, -1):
82
+ key = " ".join(patch_parts[:i])
83
+ # consume parts and go down in diff hierarchy
84
+ if key in diff_current:
85
+ patch_parts = patch_parts[i:]
86
+ diff_current = diff_current[key]
87
+ break
88
+ # no progress has been made
89
+ else:
90
+ break
91
+ if not patch_parts:
92
+ patch_lines_passed.append(patch_line)
93
+ return "\n".join(patch_lines_passed)
94
+
95
+
96
+ def apply_acl_fileconfig(config, rules):
97
+ passed = {}
98
+ for (filename, filecontent) in config.items():
99
+ (match, _) = patching.match_row_to_acl(filename, rules)
100
+ if match:
101
+ if not (match["is_reverse"] and match["attrs"]["cant_delete"]):
102
+ passed[filename] = filecontent
103
+ return passed
104
+
105
+
106
+ def get_op(line: str) -> typing.Tuple[str, str, str]:
107
+ op = " "
108
+ indent = ""
109
+ opidx = -1
110
+ rowstart = 0
111
+ for rowstart in range(len(line)):
112
+ if line[rowstart] not in diff_ops:
113
+ break
114
+ for opidx in range(rowstart):
115
+ if line[opidx] != " ":
116
+ break
117
+ if opidx >= 0:
118
+ op = line[opidx]
119
+ indent = line[:opidx] + line[opidx + 1:rowstart]
120
+ if op != " ":
121
+ indent = indent + " "
122
+ return op, indent, line[rowstart:]
123
+
124
+
125
+ def shift_op(text: str) -> str:
126
+ ret = ""
127
+ for line in text.split("\n"):
128
+ op, indent, line = get_op(line)
129
+ ret += indent + op + line + "\n"
130
+ return ret
131
+
132
+
133
+ def unshift_op(text: str) -> str:
134
+ ret = ""
135
+ for line in text.split("\n"):
136
+ op, indent, line = get_op(line)
137
+ ret += op + indent + line + "\n"
138
+ return ret
139
+
140
+
141
+ def strip_op(text: str) -> str:
142
+ ret: str = ""
143
+ for line in text.split("\n"):
144
+ op, indent, line = get_op(line)
145
+ if op != " ":
146
+ indent = indent[1:]
147
+ ret += indent + line + "\n"
148
+ return ret
149
+
150
+
151
+ def tree_to_diff(diff_tree: ConfigTree) -> Diff:
152
+ ret = []
153
+ for row, v in diff_tree.items():
154
+ op, _, row = get_op(row)
155
+ diff_op = diff_ops[op]
156
+ children = []
157
+ d_match = None
158
+ if isinstance(v, dict):
159
+ children = tree_to_diff(v)
160
+ ret.append((diff_op, row, children, d_match))
161
+ return ret
162
+
163
+
164
+ def diff_to_tree(diff: Diff) -> ConfigTree:
165
+ ret = collections.OrderedDict()
166
+ for diff_op, row, children, _ in diff:
167
+ row = ops_sign[diff_op] + row
168
+ ret[row] = diff_to_tree(children)
169
+ return ret
170
+
171
+
172
+ def _tree_expand_lists_nokia_jun(diff_tree: DiffTree):
173
+ """
174
+ Раскрываем списки Nokia/Juniper в отдельные элементы
175
+ {command: {"[a, b, c]": {}}} -> {command a: {}, command b: {}, command c: {}}
176
+
177
+ В неупорядоченном множестве префиксов также стираем ';' на конце - их не бывает в патче
178
+ {prefix-list: {"2a02::/64;": {}, "2a03::/64;": {}}} -> {prefix-list: {"2a02::/64": {}, "2a03::/64": {}}}
179
+ """
180
+ process: typing.List[DiffTree] = [diff_tree]
181
+ list_regexp = re.compile(r"^(.*)\s+\[(.+)\]$")
182
+ while process:
183
+ tree, process = process[0], process[1:]
184
+ for cmd in list(tree.keys()):
185
+ children = tree[cmd]
186
+ matches = list_regexp.search(cmd)
187
+ normalized_cmd = cmd.rstrip(";")
188
+ if matches:
189
+ for c in matches.group(2).split(" "):
190
+ if c.strip():
191
+ tree[" ".join([matches.group(1), c])] = children
192
+ if normalized_cmd != cmd:
193
+ del tree[cmd]
194
+ tree[normalized_cmd] = children
195
+ if isinstance(children, dict):
196
+ process.append(children)
@@ -0,0 +1,116 @@
1
+ """Support JSON patch (RFC 6902) and JSON Pointer (RFC 6901)."""
2
+
3
+ import copy
4
+ import json
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import jsonpatch
8
+ import jsonpointer
9
+
10
+
11
+ def format_json(data: Any, stable: bool = False) -> str:
12
+ """Serialize to json."""
13
+ return json.dumps(data, indent=4, ensure_ascii=False, sort_keys=not stable) + "\n"
14
+
15
+
16
+ def apply_json_fragment(
17
+ old: Dict[str, Any],
18
+ new_fragment: Dict[str, Any],
19
+ acl: List[str],
20
+ ) -> Dict[str, Any]:
21
+ """
22
+ Replace parts of the old document with 'new_fragment' using ACL restrictions.
23
+ """
24
+ full_new_config = copy.deepcopy(old)
25
+ for acl_item in acl:
26
+ pointer = jsonpointer.JsonPointer(acl_item)
27
+
28
+ try:
29
+ new_value = pointer.get(new_fragment)
30
+ except jsonpointer.JsonPointerException:
31
+ # no value found in new_fragment by the pointer, skip the ACL item
32
+ continue
33
+
34
+ _ensure_pointer_exists(full_new_config, pointer)
35
+ pointer.set(full_new_config, new_value)
36
+
37
+ return full_new_config
38
+
39
+
40
+ def _ensure_pointer_exists(doc: Dict[str, Any], pointer: jsonpointer.JsonPointer) -> None:
41
+ """
42
+ Ensure that document has all pointer parts (if possible).
43
+
44
+ This is workaround for errors of type:
45
+
46
+ ```
47
+ jsonpointer.JsonPointerException: member 'MY_PART' not found in {}
48
+ ```
49
+
50
+ See for details: https://github.com/stefankoegl/python-json-pointer/issues/41
51
+ """
52
+ parts_except_the_last = pointer.get_parts()[:-1]
53
+ doc_pointer: Dict[str, Any] = doc
54
+ for part in parts_except_the_last:
55
+ if part not in doc_pointer:
56
+ # create an empty object by the pointer part
57
+ doc_pointer[part] = {}
58
+
59
+ if isinstance(doc_pointer, dict):
60
+ # follow the pointer to delve deeper
61
+ doc_pointer = doc_pointer[part]
62
+ else:
63
+ # not a dict - cannot delve deeper
64
+ break
65
+
66
+
67
+ def make_patch(old: Dict[str, Any], new: Dict[str, Any]) -> List[Dict[str, Any]]:
68
+ """Generate a JSON patch by comparing the old document with the new one."""
69
+ return jsonpatch.make_patch(old, new).patch
70
+
71
+
72
+ def apply_patch(content: Optional[bytes], patch_bytes: bytes) -> bytes:
73
+ """
74
+ Apply JSON patch to file contents.
75
+
76
+ If content is None it is considered that the file does not exist.
77
+ """
78
+ old_doc: Any
79
+ if content is not None:
80
+ old_doc = json.loads(content)
81
+ else:
82
+ old_doc = None
83
+
84
+ patch_data = json.loads(patch_bytes)
85
+ patch = jsonpatch.JsonPatch(patch_data)
86
+ new_doc = patch.apply(old_doc)
87
+
88
+ new_contents = format_json(new_doc, stable=True).encode()
89
+ return new_contents
90
+
91
+
92
+ def apply_acl_filters(content: Dict[str, Any], filters: List[str]) -> Dict[str, Any]:
93
+ result = {}
94
+ for f in filters:
95
+ filter_text = f.strip()
96
+ if not filter_text:
97
+ continue
98
+
99
+ pointer = jsonpointer.JsonPointer(filter_text)
100
+
101
+ try:
102
+ part = pointer.get(copy.deepcopy(content))
103
+
104
+ sub_tree = result
105
+ for i in pointer.get_parts():
106
+ if i not in sub_tree:
107
+ sub_tree[i] = {}
108
+ sub_tree = sub_tree[i]
109
+
110
+ patch = jsonpatch.JsonPatch([{"op": "add", "path": filter_text, "value": part}])
111
+ result = patch.apply(result)
112
+ except jsonpointer.JsonPointerException:
113
+ # no value found in content by the pointer, skip the ACL item
114
+ continue
115
+
116
+ return result