annet 0.1__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/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 +89 -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 +807 -0
- annet/argparse.py +415 -0
- annet/cli.py +192 -0
- annet/cli_args.py +493 -0
- annet/configs/context.yml +18 -0
- annet/configs/logging.yaml +39 -0
- annet/connectors.py +64 -0
- annet/deploy.py +441 -0
- annet/diff.py +85 -0
- annet/executor.py +551 -0
- annet/filtering.py +40 -0
- annet/gen.py +828 -0
- annet/generators/__init__.py +987 -0
- annet/generators/common/__init__.py +0 -0
- annet/generators/common/initial.py +33 -0
- annet/hardware.py +45 -0
- annet/implicit.py +139 -0
- annet/lib.py +128 -0
- annet/output.py +170 -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 +121 -0
- annet/tabparser.py +36 -0
- annet/text_term_format.py +95 -0
- annet/tracing.py +170 -0
- annet/types.py +223 -0
- annet-0.1.dist-info/AUTHORS +21 -0
- annet-0.1.dist-info/LICENSE +21 -0
- annet-0.1.dist-info/METADATA +24 -0
- annet-0.1.dist-info/RECORD +113 -0
- annet-0.1.dist-info/WHEEL +5 -0
- annet-0.1.dist-info/entry_points.txt +6 -0
- annet-0.1.dist-info/top_level.txt +3 -0
- annet_generators/__init__.py +0 -0
- annet_generators/example/__init__.py +12 -0
- annet_generators/example/lldp.py +52 -0
- annet_nbexport/__init__.py +220 -0
- annet_nbexport/main.py +46 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Cisco": "^[Cc]isco",
|
|
3
|
+
"Cisco.CGS": " CGS",
|
|
4
|
+
"Cisco.IE": " IE",
|
|
5
|
+
"Cisco.AIR": "AIR",
|
|
6
|
+
"Cisco.ASR": " ASR",
|
|
7
|
+
"Cisco.ASR.ASR9000": " 9\\d{3}",
|
|
8
|
+
"Cisco.XRV": "XRv",
|
|
9
|
+
"Cisco.Catalyst": "Catalyst",
|
|
10
|
+
"Cisco.Catalyst.C2900": " 29\\d\\d",
|
|
11
|
+
"Cisco.Catalyst.C2900.C2950": " 2950",
|
|
12
|
+
"Cisco.Catalyst.C2900.C2960": " 2960",
|
|
13
|
+
"Cisco.Catalyst.C2900.C2960.C2960Plus": " 2960-Plus",
|
|
14
|
+
"Cisco.Catalyst.C2900.C2960.C2960S": " 2960S",
|
|
15
|
+
"Cisco.Catalyst.C2900.C2960.C2960X": " 2960X",
|
|
16
|
+
"Cisco.Catalyst.C3500": " 35\\d\\d",
|
|
17
|
+
"Cisco.Catalyst.C3600": " 36\\d\\d",
|
|
18
|
+
"Cisco.Catalyst.C3700": " 37\\d\\d",
|
|
19
|
+
"Cisco.Catalyst.C4900": " 49\\d\\d",
|
|
20
|
+
"Cisco.Catalyst.C6500": " 65\\d\\d",
|
|
21
|
+
"Cisco.Nexus": " [Nn]exus",
|
|
22
|
+
"Cisco.Nexus.N3x": " 3\\d\\d\\d",
|
|
23
|
+
"Cisco.Nexus.N3x.N3100": " 31\\d\\d",
|
|
24
|
+
"Cisco.Nexus.N3x.N3432": " 3432",
|
|
25
|
+
"Cisco.Nexus.N3x.N3500": " 35\\d\\d",
|
|
26
|
+
"Cisco.Nexus.N5x": " 5\\d\\d\\d",
|
|
27
|
+
"Cisco.Nexus.N5x.N5000": " 50\\d\\d",
|
|
28
|
+
"Cisco.Nexus.N5x.N5500": " 55\\d\\d",
|
|
29
|
+
"Cisco.Nexus.N6x": " 6\\d\\d\\d",
|
|
30
|
+
"Cisco.Nexus.N6x.N6000": " 60\\d\\d",
|
|
31
|
+
"Cisco.Nexus.N7x": " 7\\d{3}",
|
|
32
|
+
"Cisco.Nexus.N7x.N7000": " 70\\d\\d",
|
|
33
|
+
"Cisco.Nexus.N7x.N7700": " 77\\d\\d",
|
|
34
|
+
"Cisco.Nexus.N9x": " (N9K-[A-Z])?9\\d\\d\\d",
|
|
35
|
+
"Cisco.Nexus.N9x.N9316": " 9316",
|
|
36
|
+
"Cisco.Nexus.N9x.N9500": " 95\\d\\d",
|
|
37
|
+
"Cisco.Nexus.N9x.N9364": " N9K-C9364",
|
|
38
|
+
|
|
39
|
+
"Huawei": "^[Hh]uawei",
|
|
40
|
+
"Huawei.CE": " CE\\d+",
|
|
41
|
+
"Huawei.CE.CE12800": " CE128\\d{2}",
|
|
42
|
+
"Huawei.CE.CE5800": " CE58\\d\\d",
|
|
43
|
+
"Huawei.CE.CE6800": " CE68\\d\\d",
|
|
44
|
+
"Huawei.CE.CE6800.CE6850": " CE6850",
|
|
45
|
+
"Huawei.CE.CE6800.CE6860": "CE6860",
|
|
46
|
+
"Huawei.CE.CE6800.CE6865": " CE6865",
|
|
47
|
+
"Huawei.CE.CE6800.CE6870": " CE6870",
|
|
48
|
+
"Huawei.CE.CE6800.CE6865E": " CE6865E",
|
|
49
|
+
"Huawei.CE.CE6800.CE6865FM": " CE6865FM",
|
|
50
|
+
"Huawei.CE.CE7800": " CE78\\d\\d",
|
|
51
|
+
"Huawei.CE.CE8800": " CE88\\d\\d",
|
|
52
|
+
"Huawei.CE.CE8800.CE8850": " CE8850",
|
|
53
|
+
"Huawei.CE.CE8800.CE8851": " CE8851",
|
|
54
|
+
"Huawei.CE.CE9800": " CE98\\d\\d",
|
|
55
|
+
"Huawei.Quidway": " S\\d+",
|
|
56
|
+
"Huawei.Quidway.S2x": " S2\\d{3}",
|
|
57
|
+
"Huawei.Quidway.S2x.S2300": " S23\\d\\d",
|
|
58
|
+
"Huawei.Quidway.S2x.S2700": " S27\\d\\d",
|
|
59
|
+
"Huawei.Quidway.S5300": " S53\\d\\d",
|
|
60
|
+
"Huawei.Quidway.S5700": " S57\\d\\d",
|
|
61
|
+
"Huawei.Quidway.S6700": " S67\\d\\d",
|
|
62
|
+
"Huawei.NE": " NE\\d+",
|
|
63
|
+
"Huawei.NE.NE40E": " NE40E",
|
|
64
|
+
"Huawei.NE.NE8000": " NE8000",
|
|
65
|
+
"Huawei.EI": "-EI(-|$)",
|
|
66
|
+
"Huawei.HI": "-HI(-|$)",
|
|
67
|
+
"Huawei.SI": "-SI(-|$)",
|
|
68
|
+
|
|
69
|
+
"Juniper": "^[Jj]uniper",
|
|
70
|
+
"Juniper.MX": " MX",
|
|
71
|
+
"Juniper.MX.MX2000": " MX20\\d\\d",
|
|
72
|
+
"Juniper.MX.MX2000.MX2010": " MX2010",
|
|
73
|
+
"Juniper.MX.MX2000.MX2020": " MX2020",
|
|
74
|
+
"Juniper.MX.MX240": " MX240",
|
|
75
|
+
"Juniper.MX.MX480": " MX480",
|
|
76
|
+
"Juniper.MX.MX80": " MX80",
|
|
77
|
+
"Juniper.MX.MX960": " MX960",
|
|
78
|
+
"Juniper.QFX": " QFX",
|
|
79
|
+
"Juniper.QFX.QFX5200": " QFX5200",
|
|
80
|
+
"Juniper.VMX": " VMX",
|
|
81
|
+
"Juniper.VRR": " VRR",
|
|
82
|
+
"Juniper.PTX": " PTX",
|
|
83
|
+
|
|
84
|
+
"Arista": "^[Aa]rista",
|
|
85
|
+
"Arista.A7368": " 7368",
|
|
86
|
+
"Arista.A7368.X4": "X4",
|
|
87
|
+
"Arista.A7280": " DCS-7280",
|
|
88
|
+
"Arista.A7260": " DCS-7260",
|
|
89
|
+
"Arista.A7260.CX3": "CX3",
|
|
90
|
+
"Arista.A7050": " 7050",
|
|
91
|
+
"Arista.A7050.SX3": "SX3",
|
|
92
|
+
"Arista.A7060": " DCS-7060",
|
|
93
|
+
"Arista.A7060.DX4": "DX4",
|
|
94
|
+
|
|
95
|
+
"Nokia": "^[Nn]okia",
|
|
96
|
+
"Nokia.NS7750": " 7750",
|
|
97
|
+
"Nokia.SR_1s": "SR-1s",
|
|
98
|
+
|
|
99
|
+
"PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa)",
|
|
100
|
+
|
|
101
|
+
"PC.Whitebox": "([Mm]ellanox SN|[Ee]dge-?[Cc]ore)",
|
|
102
|
+
"PC.Whitebox.Mellanox": "[Mm]ellanox",
|
|
103
|
+
"PC.Whitebox.Mellanox.SN": " SN",
|
|
104
|
+
"PC.Whitebox.Mellanox.SN.SN2100": " SN2100",
|
|
105
|
+
"PC.Whitebox.Mellanox.SN.SN2410": " SN2410",
|
|
106
|
+
"PC.Whitebox.Mellanox.SN.SN3700": " SN3700",
|
|
107
|
+
"PC.Whitebox.Mellanox.SN.SN4700": " SN4700",
|
|
108
|
+
"PC.Whitebox.Edgecore": "[Ee]dge-?[Cc]ore",
|
|
109
|
+
"PC.Whitebox.Edgecore.AS": "AS",
|
|
110
|
+
"PC.Whitebox.Edgecore.AS9736": "AS9736",
|
|
111
|
+
"PC.Moxa": "[Mm]oxa",
|
|
112
|
+
"PC.Moxa.NPort": " [Nn][Pp]ort",
|
|
113
|
+
"PC.Moxa.NPort.6610": "6610",
|
|
114
|
+
|
|
115
|
+
"RouterOS": "^([Rr]outer[Oo][Ss]|[Mm]ikro[Tt]ik)",
|
|
116
|
+
|
|
117
|
+
"Aruba": "^Aruba",
|
|
118
|
+
"Aruba.AP": " I?AP-",
|
|
119
|
+
"Aruba.AP.AP200": " AP-2\\d\\d",
|
|
120
|
+
"Aruba.AP.AP200.AP224": " AP-224",
|
|
121
|
+
"Aruba.AP.AP200.AP225": " AP-225",
|
|
122
|
+
"Aruba.AP.AP300": " AP-3\\d\\d",
|
|
123
|
+
"Aruba.AP.AP300.AP367": " AP-367",
|
|
124
|
+
"Aruba.AP.AP300.AP377": " AP-377",
|
|
125
|
+
"Aruba.AP.AP500": " AP-5\\d\\d",
|
|
126
|
+
"Aruba.AP.AP500.AP514": " AP-514",
|
|
127
|
+
"Aruba.AP.AP500.AP515": " AP-515",
|
|
128
|
+
"Aruba.AP.IAP": " IAP-",
|
|
129
|
+
"Aruba.AP.IAP.IAP300": " IAP-3\\d\\d",
|
|
130
|
+
"Aruba.AP.IAP.IAP300.IAP304": " IAP-304",
|
|
131
|
+
"Aruba.AP.IAP.IAP300.IAP324": " IAP-324",
|
|
132
|
+
"Aruba.AP.IAP.IAP300.IAP325": " IAP-325",
|
|
133
|
+
|
|
134
|
+
"Ribbon": "^Ribbon",
|
|
135
|
+
"Ribbon.OPT": " OPT",
|
|
136
|
+
"Ribbon.OPT.OPT9608": "9608D?"
|
|
137
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DumpResult(str):
|
|
7
|
+
"""
|
|
8
|
+
строка, у которой repr = value
|
|
9
|
+
"""
|
|
10
|
+
def __new__(cls, value):
|
|
11
|
+
return super().__new__(cls, value)
|
|
12
|
+
|
|
13
|
+
__repr__ = str.__str__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _EnumAllAttrs:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DumpableView:
|
|
21
|
+
def _enum_attrs(self):
|
|
22
|
+
for attr in dir(self):
|
|
23
|
+
if not attr.startswith("_") and attr != "dump":
|
|
24
|
+
yield attr
|
|
25
|
+
|
|
26
|
+
def _getter_for_dump(self, attr):
|
|
27
|
+
dumper_name = "_dump_" + attr
|
|
28
|
+
if hasattr(self, dumper_name) and callable(getattr(self, dumper_name)):
|
|
29
|
+
return getattr(self, dumper_name)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def strip_dumper(attr, lines=None, chars=None, chars_in_line=None):
|
|
33
|
+
def bound_method(self):
|
|
34
|
+
lines_list = []
|
|
35
|
+
value = getattr(self, attr)
|
|
36
|
+
assert isinstance(value, str)
|
|
37
|
+
total_lines = 0
|
|
38
|
+
total_chars = 0
|
|
39
|
+
add_tail = True
|
|
40
|
+
for line in value.split("\n"):
|
|
41
|
+
if lines and total_lines >= lines:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if chars and total_chars + len(line) >= chars:
|
|
45
|
+
lines_list.append(line[chars - total_chars:])
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
if chars_in_line and len(line) > chars_in_line:
|
|
49
|
+
lines_list.append(line[chars - total_chars:] + " ...")
|
|
50
|
+
else:
|
|
51
|
+
lines_list.append(line)
|
|
52
|
+
total_lines += 1
|
|
53
|
+
total_chars += len(line) + 1
|
|
54
|
+
else:
|
|
55
|
+
add_tail = False
|
|
56
|
+
|
|
57
|
+
ret = repr("\n".join(lines_list))
|
|
58
|
+
if add_tail:
|
|
59
|
+
ret += " ..."
|
|
60
|
+
return DumpResult(ret)
|
|
61
|
+
return bound_method
|
|
62
|
+
|
|
63
|
+
def __dump_value(self, prefix, value, seen):
|
|
64
|
+
ret = []
|
|
65
|
+
op = "="
|
|
66
|
+
if isinstance(value, DumpableView):
|
|
67
|
+
# защита от рекурсии - последующие ссылки на объекты, а не новые дампы
|
|
68
|
+
if id(value) in seen:
|
|
69
|
+
value = DumpResult(seen[id(value)])
|
|
70
|
+
op = "->"
|
|
71
|
+
else:
|
|
72
|
+
seen[id(value)] = prefix
|
|
73
|
+
|
|
74
|
+
if isinstance(value, DumpableView):
|
|
75
|
+
ret += value.dump(prefix, seen=seen) # pylint: disable=no-member
|
|
76
|
+
else:
|
|
77
|
+
fmt = repr(value)
|
|
78
|
+
vtype = type(value)
|
|
79
|
+
if not isinstance(value, DumpResult) and (vtype.__module__ != "builtins") and vtype.__name__ not in fmt:
|
|
80
|
+
fmt += " # %s" % (vtype)
|
|
81
|
+
ret += ["%s %s %s" % (prefix, op, fmt)]
|
|
82
|
+
return ret
|
|
83
|
+
|
|
84
|
+
def dump(self, prefix="", value=_EnumAllAttrs, seen=None):
|
|
85
|
+
"""
|
|
86
|
+
В собственных DumpableView-классах нужно определить dump примерно так:
|
|
87
|
+
def dump(self, prefix, **kwargs):
|
|
88
|
+
ret = super().dump(prefix, **kwargs) + [ your_own_lines ]
|
|
89
|
+
"""
|
|
90
|
+
if seen is None:
|
|
91
|
+
seen = {id(self): prefix}
|
|
92
|
+
|
|
93
|
+
ret = []
|
|
94
|
+
if value is not _EnumAllAttrs:
|
|
95
|
+
ret.extend(self.__dump_value(prefix, value, seen))
|
|
96
|
+
else:
|
|
97
|
+
for attr in self._enum_attrs():
|
|
98
|
+
getter = self._getter_for_dump(attr)
|
|
99
|
+
try:
|
|
100
|
+
if getter:
|
|
101
|
+
value = getter()
|
|
102
|
+
else:
|
|
103
|
+
value = getattr(self, attr)
|
|
104
|
+
if inspect.isroutine(value):
|
|
105
|
+
continue
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
exc_tb = sys.exc_info()[2]
|
|
108
|
+
frame_index = -1
|
|
109
|
+
tb_list = traceback.extract_tb(exc_tb)
|
|
110
|
+
value = DumpResult("%r at %s:%s" % (exc, tb_list[frame_index][0], tb_list[frame_index][1]))
|
|
111
|
+
|
|
112
|
+
ret.extend(self.__dump_value("%s.%s" % (prefix, attr), value, seen))
|
|
113
|
+
return ret
|
|
114
|
+
|
|
115
|
+
# Filter out cached lru_methods from state
|
|
116
|
+
def __getstate__(self):
|
|
117
|
+
state = self.__dict__.copy()
|
|
118
|
+
for key in list(state.keys()): # For thread-safe: dictionary changed size during iteration
|
|
119
|
+
if inspect.ismethod(state[key]):
|
|
120
|
+
del state[key]
|
|
121
|
+
return state
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from annet.annlib.netdev.devdb import parse_hw_model
|
|
4
|
+
|
|
5
|
+
from .dump import DumpableView
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HardwareLeaf(DumpableView):
|
|
9
|
+
def __new__(cls, *_, **__):
|
|
10
|
+
obj = super(HardwareLeaf, cls).__new__(cls)
|
|
11
|
+
obj.__path = ()
|
|
12
|
+
obj.__true_sequences = set()
|
|
13
|
+
obj.__false_sequences = set()
|
|
14
|
+
return obj
|
|
15
|
+
|
|
16
|
+
def __init__(self, path, true_sequences, false_sequences):
|
|
17
|
+
self.__path = path
|
|
18
|
+
self.__true_sequences = true_sequences
|
|
19
|
+
self.__false_sequences = false_sequences
|
|
20
|
+
|
|
21
|
+
def __bool__(self):
|
|
22
|
+
if len(self.__path) == 0 or self.__path in self.__true_sequences:
|
|
23
|
+
return True
|
|
24
|
+
elif self.__path in self.__false_sequences:
|
|
25
|
+
return False
|
|
26
|
+
raise AttributeError("HW: " + ".".join(self.__path))
|
|
27
|
+
|
|
28
|
+
def __getattr__(self, name):
|
|
29
|
+
path = self.__path + (name,)
|
|
30
|
+
if path in self.__true_sequences or path in self.__false_sequences:
|
|
31
|
+
return HardwareLeaf(path, self.__true_sequences, self.__false_sequences)
|
|
32
|
+
try:
|
|
33
|
+
return self.__dict__[name]
|
|
34
|
+
except KeyError:
|
|
35
|
+
raise AttributeError("HW: " + ".".join(path))
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return str(" | ".join(".".join(x) for x in self.__true_sequences))
|
|
39
|
+
|
|
40
|
+
__repr__ = __str__
|
|
41
|
+
|
|
42
|
+
def dump(self, prefix, **kwargs): # pylint: disable=arguments-differ
|
|
43
|
+
ret = super().dump(prefix, **kwargs)
|
|
44
|
+
seen = set()
|
|
45
|
+
for seq in sorted(self.__true_sequences, key=len, reverse=True):
|
|
46
|
+
if any(name in seen for name in seq):
|
|
47
|
+
continue
|
|
48
|
+
seen.update(seq)
|
|
49
|
+
ret.append("%s.%s = True" % (prefix, ".".join(seq)))
|
|
50
|
+
return ret
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HardwareView(HardwareLeaf):
|
|
54
|
+
def __init__(self, hw_model, sw_version):
|
|
55
|
+
(true_sequences, false_sequences) = parse_hw_model(hw_model or "")
|
|
56
|
+
super().__init__((), true_sequences, false_sequences)
|
|
57
|
+
self.model = hw_model or ""
|
|
58
|
+
self._soft = sw_version or ""
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def vendor(self) -> Optional[str]:
|
|
62
|
+
return hw_to_vendor(self)
|
|
63
|
+
|
|
64
|
+
def __hash__(self):
|
|
65
|
+
return hash(self.model)
|
|
66
|
+
|
|
67
|
+
def __eq__(self, other):
|
|
68
|
+
return self.model == other.model
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def hw_to_vendor(hw: HardwareView) -> Optional[str]:
|
|
72
|
+
if hw.Nexus:
|
|
73
|
+
return "nexus"
|
|
74
|
+
elif hw.Cisco:
|
|
75
|
+
return "cisco"
|
|
76
|
+
elif hw.Huawei:
|
|
77
|
+
return "huawei"
|
|
78
|
+
elif hw.Juniper:
|
|
79
|
+
return "juniper"
|
|
80
|
+
elif hw.Arista:
|
|
81
|
+
return "arista"
|
|
82
|
+
elif hw.PC:
|
|
83
|
+
return "pc"
|
|
84
|
+
elif hw.Nokia:
|
|
85
|
+
return "nokia"
|
|
86
|
+
elif hw.RouterOS:
|
|
87
|
+
return "routeros"
|
|
88
|
+
elif hw.Aruba:
|
|
89
|
+
return "aruba"
|
|
90
|
+
elif hw.Ribbon:
|
|
91
|
+
return "ribbon"
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def vendor_to_hw(vendor):
|
|
96
|
+
hw = HardwareView(
|
|
97
|
+
{
|
|
98
|
+
"cisco": "Cisco",
|
|
99
|
+
"catalyst": "Cisco Catalyst",
|
|
100
|
+
"nexus": "Cisco Nexus",
|
|
101
|
+
"huawei": "Huawei",
|
|
102
|
+
"juniper": "Juniper",
|
|
103
|
+
"arista": "Arista",
|
|
104
|
+
"pc": "PC",
|
|
105
|
+
"nokia": "Nokia",
|
|
106
|
+
"aruba": "Aruba",
|
|
107
|
+
"routeros": "RouterOS",
|
|
108
|
+
"ribbon": "Ribbon",
|
|
109
|
+
}.get(vendor.lower(), vendor),
|
|
110
|
+
None,
|
|
111
|
+
)
|
|
112
|
+
return hw
|
annet/annlib/output.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses
|
|
3
|
+
import enum
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import types
|
|
9
|
+
from collections import OrderedDict as odict
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from typing import Any, List, Pattern
|
|
12
|
+
|
|
13
|
+
import colorama
|
|
14
|
+
import pygments
|
|
15
|
+
import pygments.formatters
|
|
16
|
+
import pygments.lexers.data
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
LABEL_NEW_PREFIX = "new: "
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# =====
|
|
23
|
+
class OutputWriter:
|
|
24
|
+
def __init__(self, data):
|
|
25
|
+
self.data = data
|
|
26
|
+
|
|
27
|
+
def write(self, file):
|
|
28
|
+
if isinstance(self.data, types.GeneratorType):
|
|
29
|
+
for n in self.data:
|
|
30
|
+
file.write(n)
|
|
31
|
+
return
|
|
32
|
+
file.write(self.data)
|
|
33
|
+
|
|
34
|
+
if self.data and self.data[-1:] != "\n":
|
|
35
|
+
file.write("\n")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class YamlWriter(OutputWriter):
|
|
39
|
+
def write(self, file):
|
|
40
|
+
print_as_yaml(self.data, file)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class JsonWriter(OutputWriter):
|
|
44
|
+
def write(self, file):
|
|
45
|
+
print_as_json(self.data, file)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dir_or_file_output(dest, items_count, create_dir=True, suggest_dir=False):
|
|
49
|
+
if dest is None:
|
|
50
|
+
dest = ""
|
|
51
|
+
|
|
52
|
+
dir_mode = dest.endswith(os.sep) or suggest_dir or dest not in ("-", "") and items_count > 1
|
|
53
|
+
|
|
54
|
+
if os.path.exists(dest):
|
|
55
|
+
is_dir = os.path.isdir(dest)
|
|
56
|
+
if dir_mode and not is_dir:
|
|
57
|
+
raise EnvironmentError(
|
|
58
|
+
f"Output set to dir {dest}, but file with the same name is on the way"
|
|
59
|
+
)
|
|
60
|
+
dir_mode = dir_mode or is_dir
|
|
61
|
+
elif create_dir and dest not in ("-", ""):
|
|
62
|
+
pdir = dest if dir_mode else os.path.dirname(dest)
|
|
63
|
+
if pdir:
|
|
64
|
+
os.makedirs(pdir, exist_ok=True)
|
|
65
|
+
|
|
66
|
+
return dir_mode
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def print_label(label, back_color=colorama.Back.GREEN, file=None):
|
|
70
|
+
if file is None:
|
|
71
|
+
file = sys.stdout
|
|
72
|
+
tty = bool(os.isatty(file.fileno()))
|
|
73
|
+
print("%s%s# %s %s %s%s%s" % (
|
|
74
|
+
colorama.Style.BRIGHT * tty, back_color * tty,
|
|
75
|
+
"-" * 20, label, "-" * 20,
|
|
76
|
+
colorama.Back.RESET * tty, colorama.Style.RESET_ALL * tty,
|
|
77
|
+
), file=file)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def print_err_label(label):
|
|
81
|
+
print_label(label, colorama.Back.RED)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def print_as_yaml(data, fh=sys.stdout):
|
|
85
|
+
class UnsortableTuple(tuple):
|
|
86
|
+
def __lt__(self, _):
|
|
87
|
+
raise TypeError
|
|
88
|
+
|
|
89
|
+
class Dumper(yaml.Dumper):
|
|
90
|
+
def ignore_aliases(self, data):
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def represent_ordered_dict(self, data):
|
|
94
|
+
return self.represent_dict([UnsortableTuple(item) for item in data.items()])
|
|
95
|
+
|
|
96
|
+
Dumper.add_representer(odict, Dumper.represent_ordered_dict)
|
|
97
|
+
|
|
98
|
+
def yaml_dump(data, **kwargs):
|
|
99
|
+
return yaml.dump(data, allow_unicode=True, indent=4, Dumper=Dumper, **kwargs)
|
|
100
|
+
|
|
101
|
+
if fh.isatty():
|
|
102
|
+
pygments.highlight(
|
|
103
|
+
code=yaml_dump(data, width=os.get_terminal_size().columns),
|
|
104
|
+
lexer=pygments.lexers.data.YamlLexer(),
|
|
105
|
+
formatter=pygments.formatters.TerminalFormatter(bg="dark"),
|
|
106
|
+
outfile=fh,
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
fh.write(yaml_dump(data))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Dumpable(abc.ABC):
|
|
113
|
+
def dump(self) -> Any:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_as_json(data, fh=sys.stdout):
|
|
118
|
+
"""
|
|
119
|
+
В выводе dict ключи отсортированы по имени, кроме ключей в OrderedDict,
|
|
120
|
+
которые сохраняют свой оригинальный порядок
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
class ODictKey(str):
|
|
124
|
+
"""
|
|
125
|
+
Строка с дополнительным параметром :index, по которому сортируется
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def __new__(cls, str_value, index=0):
|
|
130
|
+
self = str.__new__(cls, str_value)
|
|
131
|
+
self.ind = index
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def __lt__(self, other):
|
|
135
|
+
assert type(self) is type(other)
|
|
136
|
+
return (self.ind, self) < (other.ind, other) # pylint: disable=no-member
|
|
137
|
+
|
|
138
|
+
class UnsortableOdict(odict):
|
|
139
|
+
"""
|
|
140
|
+
Разновидность OrderedDict, ключи которого сохраняют порядок
|
|
141
|
+
при попытке строковой сортировки (содержат доп. индекс)
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(self, orig):
|
|
145
|
+
super().__init__((ODictKey(key, index), value)
|
|
146
|
+
for (index, (key, value)) in enumerate(orig.items()))
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def convert_odicts(data):
|
|
150
|
+
self = UnsortableOdict.convert_odicts
|
|
151
|
+
if isinstance(data, dict):
|
|
152
|
+
data = type(data)((key, self(value)) for (key, value) in data.items())
|
|
153
|
+
elif isinstance(data, (tuple, list)):
|
|
154
|
+
data = type(data)(self(value) for value in data)
|
|
155
|
+
if isinstance(data, odict):
|
|
156
|
+
data = UnsortableOdict(data)
|
|
157
|
+
return data
|
|
158
|
+
|
|
159
|
+
class JsonEncoder(json.JSONEncoder):
|
|
160
|
+
def default(self, o):
|
|
161
|
+
# https://github.com/PyCQA/pylint/issues/3537
|
|
162
|
+
if isinstance(o, Pattern): # pylint: disable=isinstance-second-argument-not-valid-type
|
|
163
|
+
return "(regex obj)/" + o.pattern + "/ (not a string)"
|
|
164
|
+
elif isinstance(o, Dumpable):
|
|
165
|
+
return o.dump()
|
|
166
|
+
elif dataclasses.is_dataclass(o):
|
|
167
|
+
return dataclasses.asdict(o)
|
|
168
|
+
elif isinstance(o, enum.Enum):
|
|
169
|
+
return o.value
|
|
170
|
+
|
|
171
|
+
raise TypeError("can't serialize %r to json" % o)
|
|
172
|
+
|
|
173
|
+
def encode(self, o):
|
|
174
|
+
try:
|
|
175
|
+
return super().encode(o)
|
|
176
|
+
except TypeError:
|
|
177
|
+
raise TypeError("can't serialize %r to json" % o)
|
|
178
|
+
|
|
179
|
+
dump = json.dumps(UnsortableOdict.convert_odicts(data), cls=JsonEncoder,
|
|
180
|
+
ensure_ascii=False, indent=4, sort_keys=True) + "\n"
|
|
181
|
+
if fh.isatty():
|
|
182
|
+
pygments.highlight(
|
|
183
|
+
code=dump,
|
|
184
|
+
lexer=pygments.lexers.data.JsonLexer(),
|
|
185
|
+
formatter=pygments.formatters.TerminalFormatter(bg="dark"),
|
|
186
|
+
outfile=fh,
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
fh.write(dump)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TextArgs:
|
|
193
|
+
__slots__ = ("text", "color", "offset")
|
|
194
|
+
|
|
195
|
+
def __init__(self, text, color=None, offset=None):
|
|
196
|
+
self.text = text
|
|
197
|
+
self.color = color
|
|
198
|
+
self.offset = offset # смещение от начала линии
|
|
199
|
+
|
|
200
|
+
def __repr__(self):
|
|
201
|
+
return "%s(%r, %s, %s)" % (self.__class__.__name__, self.text, self.color, self.offset)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# =====
|
|
205
|
+
@contextmanager
|
|
206
|
+
def capture_output(new_stdout: io.BufferedRandom = None, new_stderr: io.BufferedRandom = None):
|
|
207
|
+
streams = (
|
|
208
|
+
(sys.stdout, new_stdout),
|
|
209
|
+
(sys.stderr, new_stderr),
|
|
210
|
+
)
|
|
211
|
+
backup = []
|
|
212
|
+
for (std_stream, new_stream) in streams:
|
|
213
|
+
if new_stream is not None:
|
|
214
|
+
new_stream.seek(0)
|
|
215
|
+
new_stream.truncate(0)
|
|
216
|
+
std_fd = std_stream.fileno()
|
|
217
|
+
std_stream.flush()
|
|
218
|
+
backup.append((std_fd, os.fdopen(os.dup(std_fd), "w"), new_stream))
|
|
219
|
+
os.dup2(new_stream.fileno(), std_fd)
|
|
220
|
+
try:
|
|
221
|
+
yield
|
|
222
|
+
finally:
|
|
223
|
+
for (std_fd, backup_stream, stream) in backup:
|
|
224
|
+
backup_stream.flush()
|
|
225
|
+
os.dup2(backup_stream.fileno(), std_fd)
|
|
226
|
+
backup_stream.close()
|
|
227
|
+
stream.seek(0)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def format_file_diff(diff_lines: List[str]) -> List[str]:
|
|
231
|
+
colors = (
|
|
232
|
+
("+++", colorama.Fore.CYAN),
|
|
233
|
+
("---", colorama.Fore.CYAN),
|
|
234
|
+
("@@", colorama.Fore.CYAN),
|
|
235
|
+
("+", colorama.Fore.GREEN),
|
|
236
|
+
("-", colorama.Fore.RED),
|
|
237
|
+
)
|
|
238
|
+
ret = []
|
|
239
|
+
for line in diff_lines:
|
|
240
|
+
for (check, color) in colors:
|
|
241
|
+
if line.startswith(check):
|
|
242
|
+
ret.append(color + line + colorama.Fore.RESET)
|
|
243
|
+
break
|
|
244
|
+
else:
|
|
245
|
+
ret.append(line)
|
|
246
|
+
return ret
|