annet 3.5.1__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.

@@ -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,9 +243,9 @@ 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
@@ -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/hardware.py CHANGED
@@ -6,7 +6,7 @@ from annet.vendors import registry_connector
6
6
  from annet.connectors import Connector
7
7
 
8
8
 
9
- class _HardwareConnector(Connector["HarwareProvider"]):
9
+ class _HardwareConnector(Connector["HardwareProvider"]):
10
10
  name = "Hardware"
11
11
  ep_name = "hardware"
12
12
  ep_by_group_only = "annet.connectors.hardware"
@@ -15,7 +15,7 @@ class _HardwareConnector(Connector["HarwareProvider"]):
15
15
  hardware_connector = _HardwareConnector()
16
16
 
17
17
 
18
- class HarwareProvider(abc.ABC):
18
+ class HardwareProvider(abc.ABC):
19
19
  @abc.abstractmethod
20
20
  def make_hw(self, hw_model: str, sw_version: str) -> Any:
21
21
  pass
@@ -29,7 +29,7 @@ class HarwareProvider(abc.ABC):
29
29
  pass
30
30
 
31
31
 
32
- class AnnetHardwareProvider(HarwareProvider):
32
+ class AnnetHardwareProvider(HardwareProvider):
33
33
  def make_hw(self, hw_model: str, sw_version: str) -> HardwareView:
34
34
  return HardwareView(hw_model, sw_version)
35
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: annet
3
- Version: 3.5.1
3
+ Version: 3.6.0
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -21,7 +21,7 @@ Requires-Dist: yarl>=1.8.2
21
21
  Requires-Dist: adaptix==3.0.0b7
22
22
  Requires-Dist: dataclass-rest==0.4
23
23
  Provides-Extra: netbox
24
- Requires-Dist: annetbox[sync]>=0.4.0; extra == "netbox"
24
+ Requires-Dist: annetbox[sync]>=0.6.0; extra == "netbox"
25
25
  Dynamic: home-page
26
26
  Dynamic: license
27
27
  Dynamic: license-file
@@ -11,7 +11,7 @@ annet/diff.py,sha256=kD_2kxz5wc2TP10xj-BHs6IPq1yNKkXxIco8czjeC6M,9497
11
11
  annet/executor.py,sha256=INlWAZFLpHurg8GTXclbSzaeSIXgZo4ccmcRulQqr88,5130
12
12
  annet/filtering.py,sha256=ZtqxPsKdV9reZoRxtQyBg22BqyMqd-2SotYcxZ-68AQ,903
13
13
  annet/gen.py,sha256=j6SUrhEbfVQJrF2pOuWQaaBABwABk8cYzOln78jJ320,31956
14
- annet/hardware.py,sha256=e7RWc5yPdEDT5hxNqcqWv9g9DRWIgwAieA2iT4FdPqc,1158
14
+ annet/hardware.py,sha256=O2uadehcavZ10ssPr-db3XYHK8cpbG7C7XFkO-I6r_s,1161
15
15
  annet/implicit.py,sha256=i6UxQAQESXWlIBohNuFQuSEgvdsydRzyBu1r7nrR2F4,6083
16
16
  annet/lib.py,sha256=4N4X6jCCrig5rk7Ua4AofrV9zK9jhzkBq57fLsfBJjw,4812
17
17
  annet/output.py,sha256=se8EpyNS9f9kPOlOaAV0ht4DjzDoBr8F2UafiezLPYw,7743
@@ -31,26 +31,26 @@ annet/adapters/file/provider.py,sha256=3hNt0QQg46SVymLQ4Bh9G4VNYyhnB7gV5yu5OiIJp
31
31
  annet/adapters/netbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  annet/adapters/netbox/provider.py,sha256=SrxW_uBLvMTqtRiYXreL6RrZK4MpxVguF2ITnYOnVHU,2226
33
33
  annet/adapters/netbox/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- annet/adapters/netbox/common/adapter.py,sha256=-YcO3371D8hupNXKf5f4-FdFSjTto-7VKDvz29Gid38,2016
34
+ annet/adapters/netbox/common/adapter.py,sha256=L9xFMcYk7E7w6MH8N0qdg8h-CSP4nZzrOMFlw0_33EU,2600
35
35
  annet/adapters/netbox/common/client.py,sha256=PaxHG4W9H8_uunIwMBNYkLq4eQJYoO6p6gY-ciQs7Nc,2563
36
- annet/adapters/netbox/common/manufacturer.py,sha256=n1RxJSGqXilKcGSqwIc7d2jD1NCaUXG5kQcOO93WTxw,1319
37
- annet/adapters/netbox/common/models.py,sha256=SrzUmeqjdOkeb5og-pR0d6aNHeYC3C7LHbeZAvcZ0d8,7997
36
+ annet/adapters/netbox/common/manufacturer.py,sha256=9jTfzwx5XmETrjSbIJu_FhNaByaUbGQE787c5rBor-4,1137
37
+ annet/adapters/netbox/common/models.py,sha256=dC2jej8t6XR-AY8K_qzbYRq46Urvqefp_x1O-uAy4hk,8910
38
38
  annet/adapters/netbox/common/query.py,sha256=kbNQSZwkjFeDArHwA8INHUauxCxYElXtNh58pZipWdo,1867
39
39
  annet/adapters/netbox/common/status_client.py,sha256=POaqiQJ0jPcqUQH-X_fWHVnKB7TBYveNriaT0eNTlfI,769
40
- annet/adapters/netbox/common/storage_base.py,sha256=3dn42CWTEZVBig4ABhy5BMQQ3YGb28InncWZoGvrgKk,8768
40
+ annet/adapters/netbox/common/storage_base.py,sha256=GVF30gV4X828MPhUOZ3HsNhWvYp0J5k6LoRFd2UOk7c,9712
41
41
  annet/adapters/netbox/common/storage_opts.py,sha256=wfv1spElomwgVYMCgGth3SWVF0RsRgtUgq9GpFL9hJs,1520
42
42
  annet/adapters/netbox/v24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  annet/adapters/netbox/v24/models.py,sha256=RH2ooUPHOtHT0q1ZQE7v23FgmcsGenzzSgJyft13o1k,7605
44
44
  annet/adapters/netbox/v24/storage.py,sha256=zptzrW4aZUv_exuGw0DqQPm_kXZR3DwL4zRHYL6twTk,6219
45
45
  annet/adapters/netbox/v37/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- annet/adapters/netbox/v37/models.py,sha256=VqI1bT5E5pFIaDJWxWMU6bioNyrXKJuNq-_J8YIJIbw,1613
47
- annet/adapters/netbox/v37/storage.py,sha256=ymt1h3slOEvQdNRQDBq3qQ-nW58EospX6lnNZtJ5jQ0,3747
46
+ annet/adapters/netbox/v37/models.py,sha256=um19-ZHC700a7vpUiW8XMwBjxPcRe1StuchAxA3wZjY,1907
47
+ annet/adapters/netbox/v37/storage.py,sha256=XBYuDvNWngofXf7Kiy-V-AyGXfIszP8UQyf1N0Obq5c,5041
48
48
  annet/adapters/netbox/v41/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- annet/adapters/netbox/v41/models.py,sha256=nunqgxffobE2C3_g1bkHXWffStBdAJxHfx0eAbvnWAU,2068
50
- annet/adapters/netbox/v41/storage.py,sha256=h6e7V4Od5o7P3Ssr_vUQ1IBjsxdnug4YGvG1BokwKuk,3795
49
+ annet/adapters/netbox/v41/models.py,sha256=iZUP0ca3t-hF_DBBJsp1JOlZEJk4CJkWsB4NxPJtrpA,2140
50
+ annet/adapters/netbox/v41/storage.py,sha256=kK_oyWNa_Ha2vSk66ca4Uxq9OZsep3y2XsRWrLp_L-Q,5095
51
51
  annet/adapters/netbox/v42/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- annet/adapters/netbox/v42/models.py,sha256=UWrnRh1k3a048XsVmqh50dF91dunrolIJzmTLT0QExw,1920
53
- annet/adapters/netbox/v42/storage.py,sha256=iMhdrdAAYZXS_VHoqi_5azFB3BVmp4daK4TVCI9vhxM,4666
52
+ annet/adapters/netbox/v42/models.py,sha256=t8UaSfzl_8ri10VoIK1TSwjL8VRmMpJYQeS3AH01VSs,2021
53
+ annet/adapters/netbox/v42/storage.py,sha256=zScnvOqjPuS39vwXcE-QDkCUosBr-nafDdgFA4jzioY,6022
54
54
  annet/annlib/__init__.py,sha256=fT1l4xV5fqqg8HPw9HqmZVN2qwS8i6X1aIm2zGDjxKY,252
55
55
  annet/annlib/command.py,sha256=SUoMAmzo7AurMWyAiJNETdcHiwyGQe1YG9f7ZCc1gWQ,1166
56
56
  annet/annlib/diff.py,sha256=MZ6eQAU3cadQp8KaSE6uAYFtcfMDCIe_eNuVROnYkCk,4496
@@ -199,8 +199,8 @@ annet/vendors/library/optixtrans.py,sha256=VdME69Ca4HAEgoaKN21fZxnmmsqqaxOe_HZja
199
199
  annet/vendors/library/pc.py,sha256=vfv31_NPi7M-4AUDL89UcpawK2E6xvCpELA209cd1ho,1086
200
200
  annet/vendors/library/ribbon.py,sha256=DDOBq-_-FL9dCxqXs2inEWZ-pvw-dJ-A-prA7cKMhec,1216
201
201
  annet/vendors/library/routeros.py,sha256=iQa7m_4wjuvcgBOI9gyZwlw1BvzJfOkvUbyoEk-NI9I,1254
202
- annet-3.5.1.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
203
- annet-3.5.1.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
202
+ annet-3.6.0.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
203
+ annet-3.6.0.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
204
204
  annet_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
205
  annet_generators/example/__init__.py,sha256=OJ77uj8axc-FIyIu_Xdcnzmde3oQW5mk5qbODkhuVc8,355
206
206
  annet_generators/example/hostname.py,sha256=RloLzNVetEoWPLITzfJ13Nk3CC0yi-cZB1RTd6dnuhI,2541
@@ -213,8 +213,8 @@ annet_generators/rpl_example/generator.py,sha256=EWah19gOH8G-QyNyWqxCqdRi0BK7GbM
213
213
  annet_generators/rpl_example/items.py,sha256=HPgxScDvSqJPdz0c2SppDrH82DZYC4zUaniQwcWmh4A,1176
214
214
  annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
215
215
  annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
216
- annet-3.5.1.dist-info/METADATA,sha256=FzDUT_CYHtfBoKd7gyzhTmLsWcbRxt10zyZuFGRYI9g,815
217
- annet-3.5.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
218
- annet-3.5.1.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
219
- annet-3.5.1.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
220
- annet-3.5.1.dist-info/RECORD,,
216
+ annet-3.6.0.dist-info/METADATA,sha256=Lz9Yyo82ATgIm12jnzzT1Ez4XVmRLuyl93IoBKNDlL8,815
217
+ annet-3.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
218
+ annet-3.6.0.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
219
+ annet-3.6.0.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
220
+ annet-3.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5