annet 3.5.0__py3-none-any.whl → 3.6.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 (38) hide show
  1. annet/adapters/netbox/common/adapter.py +30 -3
  2. annet/adapters/netbox/common/manufacturer.py +15 -14
  3. annet/adapters/netbox/common/models.py +53 -7
  4. annet/adapters/netbox/common/storage_base.py +23 -3
  5. annet/adapters/netbox/v37/models.py +19 -3
  6. annet/adapters/netbox/v37/storage.py +36 -4
  7. annet/adapters/netbox/v41/models.py +20 -14
  8. annet/adapters/netbox/v41/storage.py +38 -4
  9. annet/adapters/netbox/v42/models.py +6 -4
  10. annet/adapters/netbox/v42/storage.py +38 -3
  11. annet/annlib/command.py +1 -0
  12. annet/annlib/rulebook/common.py +5 -5
  13. annet/bgp_models.py +2 -0
  14. annet/cli_args.py +6 -0
  15. annet/deploy.py +2 -3
  16. annet/hardware.py +3 -3
  17. annet/mesh/peer_models.py +1 -0
  18. annet/rulebook/b4com/iface.py +73 -10
  19. annet/rulebook/cisco/iface.py +1 -1
  20. annet/rulebook/generic/__init__.py +0 -0
  21. annet/rulebook/generic/misc.py +14 -0
  22. annet/rulebook/huawei/aaa.py +12 -1
  23. annet/rulebook/texts/b4com.order +4 -0
  24. annet/rulebook/texts/b4com.rul +13 -5
  25. annet/rulebook/texts/cisco.order +0 -1
  26. annet/rulebook/texts/cisco.rul +18 -17
  27. annet/rulebook/texts/huawei.deploy +8 -0
  28. annet/rulebook/texts/huawei.order +14 -0
  29. annet/rulebook/texts/huawei.rul +13 -1
  30. annet/vendors/library/b4com.py +3 -0
  31. annet/vendors/library/iosxr.py +1 -1
  32. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/METADATA +2 -2
  33. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/RECORD +38 -36
  34. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/WHEEL +1 -1
  35. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/entry_points.txt +0 -0
  36. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/licenses/AUTHORS +0 -0
  37. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/licenses/LICENSE +0 -0
  38. {annet-3.5.0.dist-info → annet-3.6.0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,19 @@
1
1
  from abc import abstractmethod, ABC
2
- from typing import Protocol, Generic, TypeVar
2
+ from typing import Generic, TypeVar
3
3
 
4
4
  from annet.annlib.netdev.views.hardware import HardwareView
5
5
  from .manufacturer import get_breed, get_hw
6
- from .models import NetboxDevice, Interface, IpAddress, Prefix
6
+ from .models import NetboxDevice, Interface, IpAddress, Prefix, \
7
+ FHRPGroupAssignment, FHRPGroup
7
8
 
8
9
  NetboxDeviceT = TypeVar("NetboxDeviceT", bound=NetboxDevice)
9
10
  InterfaceT = TypeVar("InterfaceT", bound=Interface)
10
11
  IpAddressT = TypeVar("IpAddressT", bound=IpAddress)
11
12
  PrefixT = TypeVar("PrefixT", bound=Prefix)
13
+ FHRPGroupT = TypeVar("FHRPGroupT", bound=FHRPGroup)
14
+ FHRPGroupAssignmentT = TypeVar(
15
+ "FHRPGroupAssignmentT", bound=FHRPGroupAssignment,
16
+ )
12
17
 
13
18
 
14
19
  def get_device_breed(device: NetboxDeviceT) -> str:
@@ -30,7 +35,17 @@ def get_device_hw(device: NetboxDeviceT) -> HardwareView:
30
35
  return HardwareView("", "")
31
36
 
32
37
 
33
- class NetboxAdapter(ABC, Generic[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT]):
38
+ class NetboxAdapter(
39
+ ABC,
40
+ Generic[
41
+ NetboxDeviceT,
42
+ InterfaceT,
43
+ IpAddressT,
44
+ PrefixT,
45
+ FHRPGroupT,
46
+ FHRPGroupAssignmentT,
47
+ ],
48
+ ):
34
49
  @abstractmethod
35
50
  def list_all_fqdns(self) -> list[str]:
36
51
  raise NotImplementedError()
@@ -58,3 +73,15 @@ class NetboxAdapter(ABC, Generic[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT]
58
73
  @abstractmethod
59
74
  def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixT]:
60
75
  raise NotImplementedError()
76
+
77
+ @abstractmethod
78
+ def list_fhrp_group_assignments(
79
+ self, iface_ids: list[int],
80
+ ) -> list[FHRPGroupAssignmentT]:
81
+ raise NotImplementedError()
82
+
83
+ @abstractmethod
84
+ def list_fhrp_groups(
85
+ self, ids: list[int],
86
+ ) -> list[FHRPGroupT]:
87
+ raise NotImplementedError()
@@ -14,30 +14,31 @@ def get_hw(manufacturer: str, model: str, platform_name: str):
14
14
 
15
15
 
16
16
  def get_breed(manufacturer: str, model: str):
17
- if manufacturer == "Huawei" and model.startswith("CE"):
17
+ hw = get_hw(manufacturer, model, "")
18
+ if hw.Huawei.CE:
18
19
  return "vrp85"
19
- elif manufacturer == "Huawei" and model.startswith("NE"):
20
+ elif hw.Huawei.NE:
20
21
  return "vrp85"
21
- elif manufacturer == "Huawei":
22
+ elif hw.Huawei:
22
23
  return "vrp55"
23
- elif manufacturer == "H3C":
24
+ elif hw.H3C:
24
25
  return "h3c"
25
- elif manufacturer in ("Mellanox", "NVIDIA"):
26
+ elif hw.PC.NVIDIA or hw.PC.Mellanox:
26
27
  return "cuml2"
27
- elif manufacturer == "Juniper":
28
+ elif hw.Juniper:
28
29
  return "jun10"
29
- elif manufacturer == "Cisco":
30
+ elif hw.Cisco.Nexus:
31
+ return "nxos"
32
+ elif hw.Cisco:
30
33
  return "ios12"
31
- elif manufacturer == "Adva":
32
- return "adva8"
33
- elif manufacturer == "Arista":
34
+ elif hw.Arista:
34
35
  return "eos4"
35
- elif manufacturer == "B4com":
36
+ elif hw.B4com:
36
37
  return "bcom-os"
37
- elif manufacturer == "MikroTik":
38
+ elif hw.RouterOS:
38
39
  return "routeros"
39
- elif manufacturer in ("Moxa", "Nebius"):
40
+ elif hw.PC.Moxa or hw.PC.Nebius:
40
41
  return "moxa"
41
- elif manufacturer == "PC":
42
+ elif hw.PC:
42
43
  return "pc"
43
44
  return ""
@@ -49,7 +49,6 @@ class DeviceIp(DumpableView):
49
49
  id: int
50
50
  display: str
51
51
  address: str
52
- family: int
53
52
 
54
53
  @property
55
54
  def _dump__list_key(self):
@@ -131,8 +130,49 @@ def vrf_object(vrf: str | None) -> Entity | None:
131
130
  _IpAddressT = TypeVar("_IpAddressT", bound=IpAddress)
132
131
 
133
132
 
133
+ _DeviceIPT = TypeVar("_DeviceIPT", bound=DeviceIp)
134
+
135
+
136
+ @dataclass
137
+ class FHRPGroup(Generic[_DeviceIPT]):
138
+ id: int
139
+ group_id: int
140
+ display: str
141
+ protocol: str
142
+ description: str
143
+
144
+ name: str
145
+ auth_type: str | None
146
+ auth_key: str
147
+ tags: list[EntityWithSlug]
148
+ custom_fields: dict[str, Any]
149
+ ip_addresses: list[_DeviceIPT]
150
+ comments: str | None = None
151
+
152
+
153
+ _FHRPGroupT = TypeVar("_FHRPGroupT", bound=FHRPGroup)
154
+
155
+
156
+ @dataclass
157
+ class FHRPGroupAssignment(Generic[_FHRPGroupT]):
158
+ id: int
159
+ display: str
160
+ priority: int
161
+ group: _FHRPGroupT
162
+ fhrp_group_id: int
163
+
164
+ interface_type: str | None
165
+ interface_id: int | None
166
+
167
+
168
+ _FHRPGroupAssignmentT = TypeVar(
169
+ "_FHRPGroupAssignmentT",
170
+ bound=FHRPGroupAssignment,
171
+ )
172
+
173
+
134
174
  @dataclass
135
- class Interface(Entity, Generic[_IpAddressT]):
175
+ class Interface(Entity, Generic[_IpAddressT, _FHRPGroupAssignmentT]):
136
176
  device: Entity
137
177
  enabled: bool
138
178
  description: str
@@ -143,13 +183,17 @@ class Interface(Entity, Generic[_IpAddressT]):
143
183
  tagged_vlans: Optional[List[InterfaceVlan]]
144
184
  tags: List[EntityWithSlug] = field(default_factory=list)
145
185
  display: str = ""
146
- ip_addresses: List[_IpAddressT] = field(default_factory=list)
147
186
  vrf: Optional[Entity] = None
148
187
  mtu: int | None = None
149
188
  lag: Entity | None = None
150
189
  lag_min_links: int | None = None
151
190
  speed: int | None = None
152
191
 
192
+ ip_addresses: List[_IpAddressT] = field(default_factory=list)
193
+ count_ipaddresses: int = 0
194
+ fhrp_groups: List[_FHRPGroupAssignmentT] = field(default_factory=list)
195
+ count_fhrp_groups: int = 0
196
+
153
197
  def add_addr(self, address_mask: str, vrf: str | None) -> None:
154
198
  addr = ip_interface(address_mask)
155
199
 
@@ -183,7 +227,7 @@ _InterfaceT = TypeVar("_InterfaceT", bound=Interface)
183
227
 
184
228
 
185
229
  @dataclass
186
- class NetboxDevice(Entity, Generic[_InterfaceT]):
230
+ class NetboxDevice(Entity, Generic[_InterfaceT, _DeviceIPT]):
187
231
  url: str
188
232
  storage: Storage
189
233
 
@@ -199,14 +243,16 @@ class NetboxDevice(Entity, Generic[_InterfaceT]):
199
243
  position: Optional[float]
200
244
  face: Optional[Label]
201
245
  status: Label
202
- primary_ip: Optional[DeviceIp]
203
- primary_ip4: Optional[DeviceIp]
204
- primary_ip6: Optional[DeviceIp]
246
+ primary_ip: Optional[_DeviceIPT]
247
+ primary_ip4: Optional[_DeviceIPT]
248
+ primary_ip6: Optional[_DeviceIPT]
205
249
  tags: List[EntityWithSlug]
206
250
  custom_fields: Dict[str, Any]
207
251
  created: datetime
208
252
  last_updated: datetime
209
253
  cluster: Optional[Entity]
254
+ config_context: Optional[dict[str, Any]]
255
+ config_template: Optional[dict[str, Any]]
210
256
 
211
257
  fqdn: str
212
258
  hostname: str
@@ -9,13 +9,19 @@ from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATO
9
9
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
10
10
  from annet.storage import Storage
11
11
  from .adapter import NetboxAdapter
12
- from .models import IpAddress, Interface, NetboxDevice, Prefix
12
+ from .models import (
13
+ IpAddress, Interface, NetboxDevice, Prefix, FHRPGroup, FHRPGroupAssignment,
14
+ )
13
15
 
14
16
  logger = getLogger(__name__)
15
17
  NetboxDeviceT = TypeVar("NetboxDeviceT", bound=NetboxDevice)
16
18
  InterfaceT = TypeVar("InterfaceT", bound=Interface)
17
19
  IpAddressT = TypeVar("IpAddressT", bound=IpAddress)
18
20
  PrefixT = TypeVar("PrefixT", bound=Prefix)
21
+ FHRPGroupT = TypeVar("FHRPGroupT", bound=FHRPGroup)
22
+ FHRPGroupAssignmentT = TypeVar(
23
+ "FHRPGroupAssignmentT", bound=FHRPGroupAssignment,
24
+ )
19
25
 
20
26
 
21
27
  class BaseNetboxStorage(
@@ -25,6 +31,8 @@ class BaseNetboxStorage(
25
31
  InterfaceT,
26
32
  IpAddressT,
27
33
  PrefixT,
34
+ FHRPGroupT,
35
+ FHRPGroupAssignmentT,
28
36
  ],
29
37
  ):
30
38
  """
@@ -58,7 +66,7 @@ class BaseNetboxStorage(
58
66
  token: str,
59
67
  ssl_context: Optional[ssl.SSLContext],
60
68
  threads: int,
61
- ) -> NetboxAdapter[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT]:
69
+ ) -> NetboxAdapter[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT, FHRPGroupT, FHRPGroupAssignmentT]:
62
70
  raise NotImplementedError()
63
71
 
64
72
  def __enter__(self):
@@ -129,10 +137,22 @@ class BaseNetboxStorage(
129
137
  interfaces = self.netbox.list_interfaces_by_devices(list(device_mapping))
130
138
  for interface in interfaces:
131
139
  device_mapping[interface.device.id].interfaces.append(interface)
140
+ self._fill_interface_fhrp_groups(interfaces)
132
141
  self._fill_interface_ipaddress(interfaces)
133
142
 
143
+ def _fill_interface_fhrp_groups(self, interfaces: list[InterfaceT]) -> None:
144
+ interface_mapping = {i.id: i for i in interfaces if i.count_fhrp_groups}
145
+ assignments = self.netbox.list_fhrp_group_assignments(list(interface_mapping))
146
+ group_ids = {r.fhrp_group_id for r in assignments}
147
+ groups = {
148
+ g.id: g for g in self.netbox.list_fhrp_groups(list(group_ids))
149
+ }
150
+ for assignment in assignments:
151
+ assignment.group = groups[assignment.fhrp_group_id]
152
+ interface_mapping[assignment.interface_id].fhrp_groups.append(assignment)
153
+
134
154
  def _fill_interface_ipaddress(self, interfaces: list[InterfaceT]) -> None:
135
- interface_mapping = {i.id: i for i in interfaces}
155
+ interface_mapping = {i.id: i for i in interfaces if i.count_ipaddresses}
136
156
  ips = self.netbox.list_ipaddr_by_ifaces(list(interface_mapping))
137
157
  for ip in ips:
138
158
  interface_mapping[ip.assigned_object_id].ip_addresses.append(ip)
@@ -3,10 +3,16 @@ from datetime import datetime, timezone
3
3
  from typing import Optional
4
4
 
5
5
  from annet.adapters.netbox.common.models import (
6
- IpAddress, NetboxDevice, Entity, Prefix, InterfaceType, Interface, IpFamily, Label,
6
+ IpAddress, NetboxDevice, Entity, Prefix, InterfaceType, Interface,
7
+ IpFamily, Label, FHRPGroupAssignment, DeviceIp, FHRPGroup,
7
8
  )
8
9
 
9
10
 
11
+ @dataclass
12
+ class DeviceIpV37(DeviceIp):
13
+ family: int
14
+
15
+
10
16
  @dataclass
11
17
  class PrefixV37(Prefix):
12
18
  site: Optional[Entity] = None
@@ -18,7 +24,17 @@ class IpAddressV37(IpAddress[PrefixV37]):
18
24
 
19
25
 
20
26
  @dataclass
21
- class InterfaceV37(Interface[IpAddressV37]):
27
+ class FHRPGroupV37(FHRPGroup[DeviceIpV37]):
28
+ pass
29
+
30
+
31
+ @dataclass
32
+ class FHRPGroupAssignmentV37(FHRPGroupAssignment[FHRPGroupV37]):
33
+ pass
34
+
35
+
36
+ @dataclass
37
+ class InterfaceV37(Interface[IpAddressV37, FHRPGroupAssignmentV37]):
22
38
  def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None:
23
39
  self.ip_addresses.append(IpAddressV37(
24
40
  id=0,
@@ -36,7 +52,7 @@ class InterfaceV37(Interface[IpAddressV37]):
36
52
 
37
53
 
38
54
  @dataclass
39
- class NetboxDeviceV37(NetboxDevice[InterfaceV37]):
55
+ class NetboxDeviceV37(NetboxDevice[InterfaceV37, DeviceIpV37]):
40
56
  device_role: Entity
41
57
 
42
58
  def __hash__(self):
@@ -7,10 +7,15 @@ from annetbox.v37 import models as api_models, client_sync
7
7
  from annet.adapters.netbox.common.adapter import NetboxAdapter, get_device_breed, get_device_hw
8
8
  from annet.adapters.netbox.common.storage_base import BaseNetboxStorage
9
9
  from annet.storage import Storage
10
- from .models import IpAddressV37, NetboxDeviceV37, InterfaceV37, PrefixV37
10
+ from .models import (
11
+ IpAddressV37, NetboxDeviceV37, InterfaceV37, PrefixV37,
12
+ FHRPGroupAssignmentV37, FHRPGroupV37,
13
+ )
11
14
 
12
15
 
13
- class NetboxV37Adapter(NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]):
16
+ class NetboxV37Adapter(NetboxAdapter[
17
+ NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37, FHRPGroupV37, FHRPGroupAssignmentV37,
18
+ ]):
14
19
  def __init__(
15
20
  self,
16
21
  storage: Storage,
@@ -37,6 +42,7 @@ class NetboxV37Adapter(NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37
37
42
  list[InterfaceV37],
38
43
  recipe=[
39
44
  link_constant(P[InterfaceV37].ip_addresses, factory=list),
45
+ link_constant(P[InterfaceV37].fhrp_groups, factory=list),
40
46
  link_constant(P[InterfaceV37].lag_min_links, value=None),
41
47
  ]
42
48
  )
@@ -51,6 +57,18 @@ class NetboxV37Adapter(NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37
51
57
  list[api_models.Prefix],
52
58
  list[PrefixV37],
53
59
  )
60
+ self.convert_fhrp_group_assignments = get_converter(
61
+ list[api_models.FHRPGroupAssignmentBrief],
62
+ list[FHRPGroupAssignmentV37],
63
+ recipe=[
64
+ link_constant(P[FHRPGroupAssignmentV37].group, value=None),
65
+ link_function(lambda model: model.group.id, P[FHRPGroupAssignmentV37].fhrp_group_id),
66
+ ]
67
+ )
68
+ self.convert_fhrp_groups = get_converter(
69
+ list[api_models.FHRPGroup],
70
+ list[FHRPGroupV37],
71
+ )
54
72
 
55
73
  def list_all_fqdns(self) -> list[str]:
56
74
  return [
@@ -79,13 +97,27 @@ class NetboxV37Adapter(NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37
79
97
  def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixV37]:
80
98
  return self.convert_ip_prefixes(self.netbox.ipam_all_prefixes(prefix=prefixes).results)
81
99
 
100
+ def list_fhrp_group_assignments(
101
+ self, iface_ids: list[int],
102
+ ) -> list[FHRPGroupAssignmentV37]:
103
+ raw_assignments = self.netbox.ipam_all_fhrp_group_assignments_by_interface(
104
+ interface_id=iface_ids,
105
+ )
106
+ return self.convert_fhrp_group_assignments(raw_assignments.results)
107
+
108
+ def list_fhrp_groups(self, ids: list[int]) -> list[FHRPGroupV37]:
109
+ raw_groups = self.netbox.ipam_all_fhrp_groups_by_id(id=list(ids))
110
+ return self.convert_fhrp_groups(raw_groups.results)
111
+
82
112
 
83
- class NetboxStorageV37(BaseNetboxStorage[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]):
113
+ class NetboxStorageV37(BaseNetboxStorage[
114
+ NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37, FHRPGroupV37, FHRPGroupAssignmentV37,
115
+ ]):
84
116
  def _init_adapter(
85
117
  self,
86
118
  url: str,
87
119
  token: str,
88
120
  ssl_context: ssl.SSLContext | None,
89
121
  threads: int,
90
- ) -> NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]:
122
+ ) -> NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37, FHRPGroupV37, FHRPGroupAssignmentV37]:
91
123
  return NetboxV37Adapter(self, url, token, ssl_context, threads)
@@ -2,7 +2,9 @@ from dataclasses import dataclass
2
2
  from typing import Optional
3
3
  import warnings
4
4
  from datetime import datetime, timezone
5
- from annet.adapters.netbox.common.models import Entity, Interface, InterfaceType, IpAddress, Label, NetboxDevice, DeviceIp, IpFamily, Prefix
5
+ from annet.adapters.netbox.common.models import Entity, Interface, \
6
+ InterfaceType, IpAddress, Label, NetboxDevice, DeviceIp, IpFamily, Prefix, \
7
+ FHRPGroupAssignment, FHRPGroup
6
8
 
7
9
 
8
10
  @dataclass
@@ -16,7 +18,22 @@ class IpAddressV41(IpAddress[PrefixV41]):
16
18
 
17
19
 
18
20
  @dataclass
19
- class InterfaceV41(Interface[IpAddressV41]):
21
+ class DeviceIpV41(DeviceIp):
22
+ family: IpFamily
23
+
24
+
25
+ @dataclass
26
+ class FHRPGroupV41(FHRPGroup[DeviceIpV41]):
27
+ pass
28
+
29
+
30
+ @dataclass
31
+ class FHRPGroupAssignmentV41(FHRPGroupAssignment[FHRPGroupV41]):
32
+ pass
33
+
34
+
35
+ @dataclass
36
+ class InterfaceV41(Interface[IpAddressV41, FHRPGroupAssignmentV41]):
20
37
  def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None:
21
38
  self.ip_addresses.append(IpAddressV41(
22
39
  id=0,
@@ -34,19 +51,8 @@ class InterfaceV41(Interface[IpAddressV41]):
34
51
 
35
52
 
36
53
  @dataclass
37
- class DeviceIpV41(DeviceIp):
38
- id: int
39
- display: str
40
- address: str
41
- family: IpFamily
42
-
43
-
44
- @dataclass
45
- class NetboxDeviceV41(NetboxDevice[InterfaceV41]):
54
+ class NetboxDeviceV41(NetboxDevice[InterfaceV41, DeviceIpV41]):
46
55
  role: Entity
47
- primary_ip: Optional[DeviceIpV41]
48
- primary_ip4: Optional[DeviceIpV41]
49
- primary_ip6: Optional[DeviceIpV41]
50
56
 
51
57
  @property
52
58
  def device_role(self):
@@ -6,11 +6,17 @@ from annetbox.v41 import models as api_models
6
6
 
7
7
  from annet.adapters.netbox.common.adapter import NetboxAdapter, get_device_breed, get_device_hw
8
8
  from annet.adapters.netbox.common.storage_base import BaseNetboxStorage
9
- from annet.adapters.netbox.v41.models import InterfaceV41, IpAddressV41, NetboxDeviceV41, PrefixV41
9
+ from annet.adapters.netbox.v41.models import (
10
+ InterfaceV41, IpAddressV41, NetboxDeviceV41, PrefixV41,
11
+ FHRPGroupV41, FHRPGroupAssignmentV41,
12
+ )
10
13
  from annet.storage import Storage
11
14
 
12
15
 
13
- class NetboxV41Adapter(NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]):
16
+ class NetboxV41Adapter(NetboxAdapter[
17
+ NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41,
18
+ FHRPGroupV41, FHRPGroupAssignmentV41
19
+ ]):
14
20
  def __init__(
15
21
  self,
16
22
  storage: Storage,
@@ -37,6 +43,7 @@ class NetboxV41Adapter(NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41
37
43
  list[InterfaceV41],
38
44
  recipe=[
39
45
  link_constant(P[InterfaceV41].ip_addresses, factory=list),
46
+ link_constant(P[InterfaceV41].fhrp_groups, factory=list),
40
47
  link_constant(P[InterfaceV41].lag_min_links, value=None),
41
48
  ]
42
49
  )
@@ -51,6 +58,18 @@ class NetboxV41Adapter(NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41
51
58
  list[api_models.Prefix],
52
59
  list[PrefixV41],
53
60
  )
61
+ self.convert_fhrp_group_assignments = get_converter(
62
+ list[api_models.FHRPGroupAssignmentBrief],
63
+ list[FHRPGroupAssignmentV41],
64
+ recipe=[
65
+ link_constant(P[FHRPGroupAssignmentV41].group, value=None),
66
+ link_function(lambda model: model.group.id, P[FHRPGroupAssignmentV41].fhrp_group_id),
67
+ ]
68
+ )
69
+ self.convert_fhrp_groups = get_converter(
70
+ list[api_models.FHRPGroup],
71
+ list[FHRPGroupV41],
72
+ )
54
73
 
55
74
  def list_all_fqdns(self) -> list[str]:
56
75
  return [
@@ -79,13 +98,28 @@ class NetboxV41Adapter(NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41
79
98
  def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixV41]:
80
99
  return self.convert_ip_prefixes(self.netbox.ipam_all_prefixes(prefix=prefixes).results)
81
100
 
101
+ def list_fhrp_group_assignments(
102
+ self, iface_ids: list[int],
103
+ ) -> list[FHRPGroupAssignmentV41]:
104
+ raw_assignments = self.netbox.ipam_all_fhrp_group_assignments_by_interface(
105
+ interface_id=iface_ids,
106
+ )
107
+ return self.convert_fhrp_group_assignments(raw_assignments.results)
108
+
109
+ def list_fhrp_groups(self, ids: list[int]) -> list[FHRPGroupV41]:
110
+ raw_groups = self.netbox.ipam_all_fhrp_groups_by_id(id=list(ids))
111
+ return self.convert_fhrp_groups(raw_groups.results)
112
+
82
113
 
83
- class NetboxStorageV41(BaseNetboxStorage[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]):
114
+ class NetboxStorageV41(BaseNetboxStorage[
115
+ NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41,
116
+ FHRPGroupV41, FHRPGroupAssignmentV41
117
+ ]):
84
118
  def _init_adapter(
85
119
  self,
86
120
  url: str,
87
121
  token: str,
88
122
  ssl_context: ssl.SSLContext | None,
89
123
  threads: int,
90
- ) -> NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]:
124
+ ) -> NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41, FHRPGroupV41, FHRPGroupAssignmentV41]:
91
125
  return NetboxV41Adapter(self, url, token, ssl_context, threads)
@@ -1,8 +1,10 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime, timezone
3
3
  from typing import Optional
4
- from annet.adapters.netbox.common.models import InterfaceType, IpFamily, Label, Prefix, Entity
5
- from annet.adapters.netbox.v41.models import InterfaceV41, IpAddressV41, NetboxDeviceV41
4
+ from annet.adapters.netbox.common.models import InterfaceType, IpFamily, Label, \
5
+ Prefix, Entity, Interface, IpAddress
6
+ from annet.adapters.netbox.v41.models import InterfaceV41, IpAddressV41, \
7
+ NetboxDeviceV41, FHRPGroupAssignmentV41
6
8
  import annetbox.v42.models
7
9
 
8
10
 
@@ -19,12 +21,12 @@ class PrefixV42(Prefix):
19
21
 
20
22
 
21
23
  @dataclass
22
- class IpAddressV42(IpAddressV41):
24
+ class IpAddressV42(IpAddress[PrefixV42]):
23
25
  prefix: Optional[PrefixV42] = None
24
26
 
25
27
 
26
28
  @dataclass
27
- class InterfaceV42(InterfaceV41):
29
+ class InterfaceV42(Interface[IpAddressV42, FHRPGroupAssignmentV41]):
28
30
  def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None:
29
31
  self.ip_addresses.append(IpAddressV42(
30
32
  id=0,
@@ -9,11 +9,15 @@ from annetbox.v42 import models as api_models
9
9
  from annet.adapters.netbox.common.adapter import NetboxAdapter, get_device_breed, get_device_hw
10
10
  from annet.adapters.netbox.common.storage_base import BaseNetboxStorage
11
11
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
12
+ from annet.adapters.netbox.v41.models import FHRPGroupAssignmentV41, FHRPGroupV41
12
13
  from annet.adapters.netbox.v42.models import InterfaceV42, NetboxDeviceV42, PrefixV42, IpAddressV42, Vlan, Vrf
13
14
  from annet.storage import Storage
14
15
 
15
16
 
16
- class NetboxV42Adapter(NetboxAdapter[NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42]):
17
+ class NetboxV42Adapter(NetboxAdapter[
18
+ NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42,
19
+ FHRPGroupV41, FHRPGroupAssignmentV41,
20
+ ]):
17
21
  def __init__(
18
22
  self,
19
23
  storage: Storage,
@@ -40,6 +44,7 @@ class NetboxV42Adapter(NetboxAdapter[NetboxDeviceV42, InterfaceV42, IpAddressV42
40
44
  list[InterfaceV42],
41
45
  recipe=[
42
46
  link_constant(P[InterfaceV42].ip_addresses, factory=list),
47
+ link_constant(P[InterfaceV42].fhrp_groups, factory=list),
43
48
  link_constant(P[InterfaceV42].lag_min_links, value=None),
44
49
  ]
45
50
  )
@@ -54,6 +59,18 @@ class NetboxV42Adapter(NetboxAdapter[NetboxDeviceV42, InterfaceV42, IpAddressV42
54
59
  list[api_models.Prefix],
55
60
  list[PrefixV42],
56
61
  )
62
+ self.convert_fhrp_group_assignments = get_converter(
63
+ list[api_models.FHRPGroupAssignmentBrief],
64
+ list[FHRPGroupAssignmentV41],
65
+ recipe=[
66
+ link_constant(P[FHRPGroupAssignmentV41].group, value=None),
67
+ link_function(lambda model: model.group.id, P[FHRPGroupAssignmentV41].fhrp_group_id),
68
+ ]
69
+ )
70
+ self.convert_fhrp_groups = get_converter(
71
+ list[api_models.FHRPGroup],
72
+ list[FHRPGroupV41],
73
+ )
57
74
 
58
75
  def list_all_fqdns(self) -> list[str]:
59
76
  return [
@@ -88,8 +105,23 @@ class NetboxV42Adapter(NetboxAdapter[NetboxDeviceV42, InterfaceV42, IpAddressV42
88
105
  def list_all_vlans(self) -> list[Vlan]:
89
106
  return self.netbox.ipam_all_vlans().results
90
107
 
108
+ def list_fhrp_group_assignments(
109
+ self, iface_ids: list[int],
110
+ ) -> list[FHRPGroupAssignmentV41]:
111
+ raw_assignments = self.netbox.ipam_all_fhrp_group_assignments_by_interface(
112
+ interface_id=iface_ids,
113
+ )
114
+ return self.convert_fhrp_group_assignments(raw_assignments.results)
115
+
116
+ def list_fhrp_groups(self, ids: list[int]) -> list[FHRPGroupV41]:
117
+ raw_groups = self.netbox.ipam_all_fhrp_groups_by_id(id=list(ids))
118
+ return self.convert_fhrp_groups(raw_groups.results)
119
+
91
120
 
92
- class NetboxStorageV42(BaseNetboxStorage[NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42]):
121
+ class NetboxStorageV42(BaseNetboxStorage[
122
+ NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42,
123
+ FHRPGroupV41, FHRPGroupAssignmentV41,
124
+ ]):
93
125
  netbox: NetboxV42Adapter
94
126
 
95
127
  def __init__(self, opts: Optional[NetboxStorageOpts] = None):
@@ -103,7 +135,10 @@ class NetboxStorageV42(BaseNetboxStorage[NetboxDeviceV42, InterfaceV42, IpAddres
103
135
  token: str,
104
136
  ssl_context: ssl.SSLContext | None,
105
137
  threads: int,
106
- ) -> NetboxAdapter[NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42]:
138
+ ) -> NetboxAdapter[
139
+ NetboxDeviceV42, InterfaceV42, IpAddressV42, PrefixV42,
140
+ FHRPGroupV41, FHRPGroupAssignmentV41,
141
+ ]:
107
142
  return NetboxV42Adapter(self, url, token, ssl_context, threads)
108
143
 
109
144
  def resolve_all_vlans(self) -> list[Vlan]:
annet/annlib/command.py CHANGED
@@ -12,6 +12,7 @@ class Question:
12
12
  question: str # frame it using / if it is a regular expression
13
13
  answer: str
14
14
  is_regexp: Optional[bool] = False
15
+ not_send_nl: bool = False
15
16
 
16
17
 
17
18
  @dataclass
@@ -10,7 +10,7 @@ from annet.vendors import registry_connector
10
10
  # =====
11
11
  def default(rule, key, diff, **_):
12
12
  r"""
13
- Функция default() обеспичвает базовую логику обработки всех правил. Ее можно заменить с помощью
13
+ Функция default() обеспечивает базовую логику обработки всех правил. Ее можно заменить с помощью
14
14
  параметра %logic в текстовом рулбуке. Она вызывается для каждой команды с уникальным ключом и
15
15
  должна возвратить сгенерированный текст патча на основе предоставленного диффа, и, при необходимости,
16
16
  вызвать обработку дочерних правил/данных.
@@ -49,8 +49,8 @@ def default(rule, key, diff, **_):
49
49
  """
50
50
  for op in [Op.ADDED, Op.REMOVED, Op.AFFECTED, Op.MOVED]:
51
51
  # Дефолтная функция генерации патчей считает, что не бывает команд с одинаковыми
52
- # ключами и разным значением. при этом unchanged мы так не проверяем поскольку
53
- # такие случаи возможны когда у нас подмешиваются implicit команды
52
+ # ключами и разным значением. При этом unchanged мы так не проверяем, поскольку
53
+ # такие случаи возможны, когда у нас подмешиваются implicit команды
54
54
  assert 0 <= len(diff[op]) <= 1, "Too many %s actions for rows %r" % (op, [x["row"] for x in diff[op]])
55
55
  if diff[Op.AFFECTED]:
56
56
  # При изменении блока нужно вызвать обработку чилдов
@@ -60,7 +60,7 @@ def default(rule, key, diff, **_):
60
60
  # При модификации строки удаление нас не интересует, добавление проходит как affected
61
61
  yield (True, diff[key][0]["row"], diff[key][0]["children"])
62
62
  elif diff[Op.REMOVED]:
63
- # При удалении или перемещеннии блока просто снести строку
63
+ # При удалении или перемещении блока просто снести строку
64
64
  yield (False, rule["reverse"].format(*key), None)
65
65
 
66
66
 
@@ -179,7 +179,7 @@ def multiline_diff(old: odict, new: odict, diff_pre: odict, _pops: tuple[Op, ...
179
179
  Особая логика diff'a для хуавейных мультилайнов.
180
180
  Она трактует все дочерние элементы %multiline-команды как
181
181
  одну общую команду, покидывая внутрь тот Op который был
182
- определен на вернем уровне
182
+ определен на верхнем уровне
183
183
  """
184
184
 
185
185
  def process_multiline(op, tree):