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

@@ -132,6 +132,9 @@
132
132
  "PC.Whitebox.Ufispace.S": " S",
133
133
  "PC.Whitebox.Ufispace.S.S9100": " S91\\d\\d",
134
134
  "PC.Whitebox.Ufispace.S.S9100.S9110_32X": " S9110-32X",
135
+ "PC.Whitebox.Ufispace.S.S9300": " S93\\d\\d",
136
+ "PC.Whitebox.Ufispace.S.S9300.S9301_32DB": " S9301-32DB",
137
+ "PC.Whitebox.Ufispace.S.S9300.S9321_64EO": " S9321-64EO",
135
138
  "PC.Nebius": "^Nebius",
136
139
  "PC.Nebius.NB-E-BR-DCU-AST2600": "^Nebius NB-E-BR-DCU-AST2600",
137
140
 
annet/bgp_models.py CHANGED
@@ -3,6 +3,77 @@ from dataclasses import dataclass, field
3
3
  from typing import Literal, Union, Optional
4
4
 
5
5
 
6
+ class VidRange:
7
+ def __init__(self, start: int, stop: int) -> None:
8
+ self.start = start
9
+ self.stop = stop
10
+
11
+ def is_single(self):
12
+ return self.start == self.stop
13
+
14
+ def __iter__(self):
15
+ return iter(range(self.start, self.stop + 1))
16
+
17
+ def __str__(self):
18
+ if self.is_single():
19
+ return str(self.start)
20
+ return f"{self.start}-{self.stop}"
21
+
22
+ def __repr__(self):
23
+ return f"VlanRange({self.start}, {self.stop})"
24
+
25
+ def __eq__(self, other: object) -> bool:
26
+ if type(other) is VidRange:
27
+ return self.start == other.start and self.stop == other.stop
28
+ return NotImplemented
29
+
30
+
31
+ def _parse_vlan_ranges(ranges: str) -> Iterable[VidRange]:
32
+ for range in ranges.split(","):
33
+ start, sep, stop = range.strip().partition("-")
34
+ try:
35
+ if not sep:
36
+ int_start = int(start)
37
+ yield VidRange(int_start, int_start)
38
+ elif not stop or not start:
39
+ raise ValueError(f"Cannot parse range {range!r}. Expected `start-stop`")
40
+ else:
41
+ yield VidRange(int(start), int(stop))
42
+ except ValueError:
43
+ raise ValueError(f"Cannot parse range {range!r}. Expected `vid1-vid2` or `vid`")
44
+
45
+
46
+ class VidCollection:
47
+ @staticmethod
48
+ def parse(ranges: int | str) -> "VidCollection":
49
+ if isinstance(ranges, int):
50
+ return VidCollection([VidRange(ranges, ranges)])
51
+ elif isinstance(ranges, str):
52
+ return VidCollection(list(_parse_vlan_ranges(ranges)))
53
+ elif isinstance(ranges, VidCollection):
54
+ return VidCollection(ranges.ranges)
55
+ else:
56
+ raise TypeError(f"Expected str or int, got {type(ranges)}")
57
+
58
+ def __init__(self, ranges: list[VidRange]) -> None:
59
+ self.ranges = ranges
60
+
61
+ def __str__(self):
62
+ return ",".join(map(str, self.ranges))
63
+
64
+ def __repr__(self):
65
+ return f"VlanCollection({str(self)!r})"
66
+
67
+ def __iter__(self):
68
+ for range in self.ranges:
69
+ yield from range
70
+
71
+ def __eq__(self, other: object) -> bool:
72
+ if type(other) is VidCollection:
73
+ return self.ranges == other.ranges
74
+ return False
75
+
76
+
6
77
  class ASN(int):
7
78
  """
8
79
  Stores ASN number and formats it as в AS1.AS2
@@ -235,6 +306,17 @@ class PeerGroup:
235
306
  mtu: int = 0
236
307
 
237
308
 
309
+ @dataclass
310
+ class L2VpnOptions:
311
+ name: str
312
+ vid: VidCollection
313
+ l2vni: int # VNI, possible values are 1 to 2**24-1
314
+ route_distinguisher: str = "" # like in VrfOptions
315
+ rt_import: list[str] = field(default_factory=list) # like in VrfOptions
316
+ rt_export: list[str] = field(default_factory=list) # like in VrfOptions
317
+ advertise_host_routes: bool = True # advertise IP+MAC routes into L3VNI
318
+
319
+
238
320
  @dataclass
239
321
  class VrfOptions:
240
322
  vrf_name: str
@@ -274,8 +356,8 @@ class GlobalOptions:
274
356
  multipath: int = 0
275
357
  router_id: str = ""
276
358
  vrf: dict[str, VrfOptions] = field(default_factory=dict)
277
-
278
359
  groups: list[PeerGroup] = field(default_factory=list)
360
+ l2vpn: dict[str, L2VpnOptions] = field(default_factory=dict)
279
361
 
280
362
 
281
363
  @dataclass
@@ -76,10 +76,21 @@ class VrfOptions(_FamiliesMixin, BaseMeshModel):
76
76
  groups: Annotated[dict[str, MeshPeerGroup], DictMerge(Merge())]
77
77
 
78
78
 
79
+ class L2VpnOptions(BaseMeshModel):
80
+ name: str
81
+ vid: str | int # VLAN ID, possible values are 1 to 4094, ranges can be set as strings
82
+ l2vni: int # VNI, possible values are 1 to 2**24-1
83
+ route_distinguisher: str # like in VrfOptions
84
+ rt_import: Annotated[tuple[str, ...], Concat()] # like in VrfOptions
85
+ rt_export: Annotated[tuple[str, ...], Concat()] # like in VrfOptions
86
+ advertise_host_routes: bool # advertise IP+MAC routes into L3VNI
87
+
88
+
79
89
  class GlobalOptionsDTO(_FamiliesMixin, BaseMeshModel):
80
90
  def __init__(self, **kwargs):
81
91
  kwargs.setdefault("groups", KeyDefaultDict(lambda x: MeshPeerGroup(name=x)))
82
92
  kwargs.setdefault("vrf", KeyDefaultDict(lambda x: VrfOptions(vrf_name=x)))
93
+ kwargs.setdefault("l2vpn", KeyDefaultDict(lambda x: L2VpnOptions(name=x)))
83
94
  super().__init__(**kwargs)
84
95
 
85
96
  as_path_relax: bool
@@ -89,3 +100,4 @@ class GlobalOptionsDTO(_FamiliesMixin, BaseMeshModel):
89
100
  router_id: str
90
101
  vrf: Annotated[dict[str, VrfOptions], DictMerge(Merge())]
91
102
  groups: Annotated[dict[str, MeshPeerGroup], DictMerge(Merge())]
103
+ l2vpn: Annotated[dict[str, L2VpnOptions], DictMerge(Merge())]
@@ -7,7 +7,7 @@ from adaptix import Retort, loader, Chain, name_mapping, as_is_loader
7
7
  from .peer_models import DirectPeerDTO, IndirectPeerDTO, VirtualPeerDTO, VirtualLocalDTO
8
8
  from ..bgp_models import (
9
9
  Aggregate, GlobalOptions, VrfOptions, FamilyOptions, Peer, PeerGroup, ASN, PeerOptions,
10
- Redistribute, BFDTimers,
10
+ Redistribute, BFDTimers, L2VpnOptions, VidCollection,
11
11
  )
12
12
 
13
13
 
@@ -49,8 +49,10 @@ retort = Retort(
49
49
  recipe=[
50
50
  loader(InterfaceChanges, ObjMapping, Chain.FIRST),
51
51
  loader(ASN, ASN),
52
+ loader(VidCollection, VidCollection.parse),
52
53
  loader(GlobalOptions, ObjMapping, Chain.FIRST),
53
54
  loader(VrfOptions, ObjMapping, Chain.FIRST),
55
+ loader(L2VpnOptions, ObjMapping, Chain.FIRST),
54
56
  loader(FamilyOptions, ObjMapping, Chain.FIRST),
55
57
  loader(Aggregate, ObjMapping, Chain.FIRST),
56
58
  loader(PeerOptions, ObjMapping, Chain.FIRST),
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass
2
+
3
+ from annet.annlib.types import Op
4
+
5
+
6
+ @dataclass
7
+ class UserConfig:
8
+ name: str
9
+ privilege: int
10
+ role: str
11
+ secret_type: str
12
+ secret: str
13
+
14
+
15
+ def _parse_user_config(config_line):
16
+ """Convert a user config line into a dataclass. Config example:
17
+
18
+ username someuser privilege 15 role network-admin secret sha512 $6$....
19
+
20
+ privilege could be omitted if equal to 1
21
+ role could be omitted
22
+ secret could be omitted, 'nopassword' is provided instead
23
+ """
24
+ splstr = config_line.split()
25
+ name = splstr[1]
26
+ priv = 1
27
+ role = ""
28
+ secret_type = ""
29
+ secret = ""
30
+ if "privilege" in splstr:
31
+ pos = splstr.index("privilege")
32
+ priv = int(splstr[pos + 1])
33
+ if "role" in splstr:
34
+ pos = splstr.index("role")
35
+ role = splstr[pos + 1]
36
+ if "secret" in splstr:
37
+ pos = splstr.index("secret")
38
+ secret_type = splstr[pos + 1]
39
+ secret = splstr[pos + 2]
40
+ return UserConfig(name=name, privilege=priv, role=role, secret_type=secret_type, secret=secret)
41
+
42
+
43
+ def user(key, diff, **_):
44
+ if diff[Op.ADDED] and not diff[Op.REMOVED]:
45
+ for add in diff[Op.ADDED]:
46
+ yield (True, add["row"], None)
47
+ elif diff[Op.REMOVED] and not diff[Op.ADDED]:
48
+ for _ in diff[Op.REMOVED]:
49
+ yield (False, f"no username {key[0]}", None)
50
+ else:
51
+ for num, add in enumerate(diff[Op.ADDED]):
52
+ new_user = _parse_user_config(add["row"])
53
+ old_user = _parse_user_config(diff[Op.REMOVED][num]["row"])
54
+ if new_user.privilege != old_user.privilege:
55
+ yield (True, f"username {key[0]} privilege {new_user.privilege}", None)
56
+ if new_user.role != old_user.role and not new_user.role:
57
+ yield (True, f"no username {key[0]} role", None)
58
+ elif new_user.role != old_user.role:
59
+ yield (True, f"username {key[0]} role {new_user.role}", None)
60
+ if new_user.secret != old_user.secret and not new_user.secret:
61
+ yield (True, f"username {key[0]} nopassword", None)
62
+ elif new_user.secret != old_user.secret:
63
+ yield (True, f"username {key[0]} secret {new_user.secret_type} {new_user.secret}", None)
@@ -5,7 +5,7 @@ qos profile *
5
5
  ~ %global
6
6
 
7
7
  username * ssh-key
8
- username *
8
+ username * %logic=arista.aaa.user
9
9
 
10
10
  aaa group ~
11
11
  ~ %global
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  annet/__init__.py,sha256=0OKkFkqog8As7B6ApdpKQkrEAcEELUREWp82D8WvGA8,1846
2
2
  annet/annet.py,sha256=TMdEuM7GJQ4TjRVmuK3bCTZN-21lxjQ9sXqEdILUuBk,725
3
3
  annet/argparse.py,sha256=v1MfhjR0B8qahza0WinmXClpR8UiDFhmwDDWtNroJPA,12855
4
- annet/bgp_models.py,sha256=NBPbMWwzo4uWFslrpE4blQO1k4CxJqeleRBDhDg9Rd0,9665
4
+ annet/bgp_models.py,sha256=oibTSdipNkGL4t8Xn94bhyKHMOtwbPBqmYaAy4FmTxQ,12361
5
5
  annet/cli.py,sha256=hDpjIr3w47lgQ_CvCQS1SXFDK-SJrf5slbT__5u6GIA,12342
6
6
  annet/cli_args.py,sha256=KQlihxSl-Phhq1-9oJDdNSbIllEX55LlPfH6viEKOuw,13483
7
7
  annet/connectors.py,sha256=aoiDVLPizx8CW2p8SAwGCzyO_WW8H9xc2aujbGC4bDg,4882
@@ -56,7 +56,7 @@ annet/annlib/types.py,sha256=VHU0CBADfYmO0xzB_c5f-mcuU3dUumuJczQnqGlib9M,852
56
56
  annet/annlib/netdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  annet/annlib/netdev/db.py,sha256=fI_u5aya4l61mbYSjj4JwlVfi3s7obt2jqERSuXGRUI,1634
58
58
  annet/annlib/netdev/devdb/__init__.py,sha256=aKYjjLbJebdKBjnGDpVLQdSqrV2JL24spGm58tmMWVU,892
59
- annet/annlib/netdev/devdb/data/devdb.json,sha256=4zTtgZw0SagYZuyItYiwoFxUAsQ1NTXfn2MjRSh-b4U,6387
59
+ annet/annlib/netdev/devdb/data/devdb.json,sha256=E9dnmAbAJpg_-F82ZZIjUaKFL02vDC7W7yfMyjaah2Q,6561
60
60
  annet/annlib/netdev/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  annet/annlib/netdev/views/dump.py,sha256=rIlyvnA3uM8bB_7oq1nS2KDxTp6dQh2hz-FbNhYIpOU,4630
62
62
  annet/annlib/netdev/views/hardware.py,sha256=3JCZLH7deIHhCguwPJTUX-WDvWjG_xt6BdSEZSO6zkQ,4226
@@ -84,10 +84,10 @@ annet/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
84
84
  annet/generators/common/initial.py,sha256=qYBxXFhyOPy34cxc6hsIXseod-lYCmmbuNHpM0uteY0,1244
85
85
  annet/mesh/__init__.py,sha256=lcgdnBIxc2MAN7Er1bcErEKPqrjWO4uIp_1FldMXTYg,557
86
86
  annet/mesh/basemodel.py,sha256=E6NTOneiMDwB1NCpjDRECoaeQ0f3n_fmTLnKTrSHTU4,4917
87
- annet/mesh/device_models.py,sha256=aFbwVWNSDBcph_Kvv6qZT-uUz0Tbg3z45EvP4i1z2ao,3600
87
+ annet/mesh/device_models.py,sha256=q_E4E_glVZRVUjVbWhvbAt4Qv8wx7IcY689EBx-OKuo,4216
88
88
  annet/mesh/executor.py,sha256=z_3e2neRR4GoEQfJlvfbalelKzOz5yb78zm9lIcLYw4,17578
89
89
  annet/mesh/match_args.py,sha256=CR3kdIV9NGtyk9E2JbcOQ3TRuYEryTWP3m2yCo2VCWg,5751
90
- annet/mesh/models_converter.py,sha256=3q2zs7K8S3pfYSUKKRdtl5CJGbeg4TtYxofAVs_MBsk,3085
90
+ annet/mesh/models_converter.py,sha256=itfrxDd5zdTQpFNmo-YXIFQDpYyBuQ6g7xpcjxvK6uA,3221
91
91
  annet/mesh/peer_models.py,sha256=Div4o1t6Z0TWvy-8WKg4-n9WOd2PKCmIpfbkILDlDtk,2791
92
92
  annet/mesh/port_processor.py,sha256=RHiMS5W8qoDkTKiarQ748bcr8bNx4g_R4Y4vZg2k4TU,478
93
93
  annet/mesh/registry.py,sha256=xmWF7yxWXmwqX2_jyMAKrbGd2G9sjb4rYDx4Xk61QKc,9607
@@ -113,6 +113,7 @@ annet/rulebook/common.py,sha256=zK1s2c5lc5HQbIlMUQ4HARQudXSgOYiZ_Sxc2I_tHqg,721
113
113
  annet/rulebook/deploying.py,sha256=XV0XQvc3YvwM8SOgOQlc-fCW4bnjQg_1CTZkTwMp14A,2972
114
114
  annet/rulebook/patching.py,sha256=ve_D-9lnWs6Qb_NwqOLUWX-b_tI_L3pwQ7cgUPnqndY,4970
115
115
  annet/rulebook/arista/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
+ annet/rulebook/arista/aaa.py,sha256=7fBTwBnz9SOqYbwG8GBeEQTJG0e4uC4HkuZJeMG-kAY,2250
116
117
  annet/rulebook/arista/iface.py,sha256=bgj6a3r__-OE6VyYbSfnD6ps2QJKyX028W7IFJww-UY,720
117
118
  annet/rulebook/aruba/__init__.py,sha256=ILggeID6kNWcDBGzhXm_mebcfkuLSq5prBFU5DyPFs4,500
118
119
  annet/rulebook/aruba/ap_env.py,sha256=5fUVLhXH4-0DAtv8t0yoqJUibaRMuzF8Q7mGFzNsEN8,4692
@@ -138,7 +139,7 @@ annet/rulebook/routeros/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
138
139
  annet/rulebook/routeros/file.py,sha256=zK7RwBk1YaVoDSFSg1u7Pt8u0Fk3nhhu27aJRngemwc,137
139
140
  annet/rulebook/texts/arista.deploy,sha256=OS9eFyJpEPztcOHkBwajw_RTJfTT7ivaMHfx4_HXaUg,792
140
141
  annet/rulebook/texts/arista.order,sha256=e7e5pnzMZAfmXJ0sUaaWmep6JMeErNnd7DkouUVIZsU,1472
141
- annet/rulebook/texts/arista.rul,sha256=QQbro8eFlc7DCHk0-CTHX_rnj5rqumRzXlY60ga72jo,815
142
+ annet/rulebook/texts/arista.rul,sha256=fGZe1L-x75rKQuRC5oLJMv1gSt1tJ0htOXfKt-tho64,853
142
143
  annet/rulebook/texts/aruba.deploy,sha256=hI432Bq-of_LMXuUflCu7eNSEFpx6qmj0KItEw6sgHI,841
143
144
  annet/rulebook/texts/aruba.order,sha256=ZMakkn0EJ9zomgY6VssoptJImrHrUmYnCqivzLBFTRo,1158
144
145
  annet/rulebook/texts/aruba.rul,sha256=zvGVpoYyJvMoL0fb1NQ8we_GCLZXno8nwWpZIOScLQQ,2584
@@ -177,10 +178,10 @@ annet_generators/rpl_example/generator.py,sha256=zndIGfV4ZlTxPgAGYs7bMQvTc_tYScO
177
178
  annet_generators/rpl_example/items.py,sha256=Ez1RF5YhcXNCusBmeApIjRL3rBlMazNZd29Gpw1_IsA,766
178
179
  annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
179
180
  annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
180
- annet-1.0.3.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
181
- annet-1.0.3.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
182
- annet-1.0.3.dist-info/METADATA,sha256=-u0h5cpMTSZeM6mxBvqvpPQ7hC1gwrza2aeHubezCDs,851
183
- annet-1.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
184
- annet-1.0.3.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
185
- annet-1.0.3.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
186
- annet-1.0.3.dist-info/RECORD,,
181
+ annet-1.0.4.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
182
+ annet-1.0.4.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
183
+ annet-1.0.4.dist-info/METADATA,sha256=X4P627F_6XM4xksFYwLxf9vqw-u7ou3br5iUkqwdFaA,851
184
+ annet-1.0.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
185
+ annet-1.0.4.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
186
+ annet-1.0.4.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
187
+ annet-1.0.4.dist-info/RECORD,,
File without changes
File without changes
File without changes