annet 2.3.1__py3-none-any.whl → 2.5.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.
- annet/adapters/netbox/common/adapter.py +60 -0
- annet/adapters/netbox/common/models.py +47 -55
- annet/adapters/netbox/common/status_client.py +7 -0
- annet/adapters/netbox/common/storage_base.py +239 -0
- annet/adapters/netbox/provider.py +20 -7
- annet/adapters/netbox/v24/models.py +295 -0
- annet/adapters/netbox/v24/storage.py +8 -2
- annet/adapters/netbox/v37/models.py +60 -0
- annet/adapters/netbox/v37/storage.py +70 -300
- annet/adapters/netbox/v41/__init__.py +0 -0
- annet/adapters/netbox/v41/models.py +78 -0
- annet/adapters/netbox/v41/storage.py +91 -0
- annet/adapters/netbox/v42/__init__.py +0 -0
- annet/adapters/netbox/v42/models.py +63 -0
- annet/adapters/netbox/v42/storage.py +91 -0
- annet/annlib/netdev/devdb/data/devdb.json +2 -0
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info}/METADATA +4 -3
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info}/RECORD +23 -13
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info}/WHEEL +1 -1
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info}/entry_points.txt +0 -0
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info/licenses}/AUTHORS +0 -0
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info/licenses}/LICENSE +0 -0
- {annet-2.3.1.dist-info → annet-2.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,321 +1,91 @@
|
|
|
1
1
|
import ssl
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from ipaddress import ip_interface
|
|
4
|
-
from logging import getLogger
|
|
5
|
-
from typing import Any, Optional, List, Union, Dict, cast
|
|
6
2
|
|
|
7
3
|
from adaptix import P
|
|
8
|
-
from adaptix.conversion import
|
|
9
|
-
from annetbox.v37 import models as api_models
|
|
10
|
-
from annetbox.v37.client_sync import NetboxV37
|
|
4
|
+
from adaptix.conversion import get_converter, link_function, link_constant, link
|
|
5
|
+
from annetbox.v37 import models as api_models, client_sync
|
|
11
6
|
|
|
12
|
-
from annet.adapters.netbox.common import
|
|
13
|
-
from annet.adapters.netbox.common.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATOR
|
|
17
|
-
from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
|
|
18
|
-
from annet.annlib.netdev.views.hardware import HardwareView
|
|
19
|
-
from annet.storage import Storage, Device, Interface
|
|
7
|
+
from annet.adapters.netbox.common.adapter import NetboxAdapter, get_device_breed, get_device_hw
|
|
8
|
+
from annet.adapters.netbox.common.storage_base import BaseNetboxStorage
|
|
9
|
+
from annet.storage import Storage
|
|
10
|
+
from .models import IpAddressV37, NetboxDeviceV37, InterfaceV37, PrefixV37
|
|
20
11
|
|
|
21
|
-
logger = getLogger(__name__)
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
breed: str = ""
|
|
45
|
-
hw = HardwareView("", "")
|
|
46
|
-
if device.platform:
|
|
47
|
-
platform_name = device.platform.name
|
|
48
|
-
if device.device_type and device.device_type.manufacturer:
|
|
49
|
-
breed = get_breed(
|
|
50
|
-
device.device_type.manufacturer.name,
|
|
51
|
-
device.device_type.model,
|
|
13
|
+
class NetboxV37Adapter(NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
storage: Storage,
|
|
17
|
+
url: str,
|
|
18
|
+
token: str,
|
|
19
|
+
ssl_context: ssl.SSLContext | None,
|
|
20
|
+
threads: int,
|
|
21
|
+
):
|
|
22
|
+
self.netbox = client_sync.NetboxV37(url=url, token=token, ssl_context=ssl_context, threads=threads)
|
|
23
|
+
self.convert_device = get_converter(
|
|
24
|
+
api_models.Device,
|
|
25
|
+
NetboxDeviceV37,
|
|
26
|
+
recipe=[
|
|
27
|
+
link_function(get_device_breed, P[NetboxDeviceV37].breed),
|
|
28
|
+
link_function(get_device_hw, P[NetboxDeviceV37].hw),
|
|
29
|
+
link_constant(P[NetboxDeviceV37].interfaces, factory=list),
|
|
30
|
+
link_constant(P[NetboxDeviceV37].storage, value=storage),
|
|
31
|
+
link(P[api_models.Device].name, P[NetboxDeviceV37].hostname),
|
|
32
|
+
link(P[api_models.Device].name, P[NetboxDeviceV37].fqdn),
|
|
33
|
+
]
|
|
52
34
|
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
self.convert_interfaces = get_converter(
|
|
36
|
+
list[api_models.Interface],
|
|
37
|
+
list[InterfaceV37],
|
|
38
|
+
recipe=[
|
|
39
|
+
link_constant(P[InterfaceV37].ip_addresses, factory=list),
|
|
40
|
+
link_constant(P[InterfaceV37].lag_min_links, value=None),
|
|
41
|
+
]
|
|
57
42
|
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
storage=storage,
|
|
64
|
-
)
|
|
65
|
-
return res
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@impl_converter(
|
|
69
|
-
recipe=[link_constant(P[models.Interface].lag_min_links, value=None)],
|
|
70
|
-
)
|
|
71
|
-
def extend_interface(
|
|
72
|
-
interface: api_models.Interface,
|
|
73
|
-
ip_addresses: List[models.IpAddress],
|
|
74
|
-
) -> models.Interface:
|
|
75
|
-
...
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@impl_converter
|
|
79
|
-
def extend_ip_address(
|
|
80
|
-
ip_address: models.IpAddress, prefix: Optional[models.Prefix],
|
|
81
|
-
) -> models.IpAddress:
|
|
82
|
-
...
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class NetboxStorageV37(Storage):
|
|
86
|
-
def __init__(self, opts: Optional[NetboxStorageOpts] = None):
|
|
87
|
-
ctx: Optional[ssl.SSLContext] = None
|
|
88
|
-
url = ""
|
|
89
|
-
token = ""
|
|
90
|
-
self.exact_host_filter = False
|
|
91
|
-
threads = 1
|
|
92
|
-
if opts:
|
|
93
|
-
if opts.insecure:
|
|
94
|
-
ctx = ssl.create_default_context()
|
|
95
|
-
ctx.check_hostname = False
|
|
96
|
-
ctx.verify_mode = ssl.CERT_NONE
|
|
97
|
-
url = opts.url
|
|
98
|
-
token = opts.token
|
|
99
|
-
threads = opts.threads
|
|
100
|
-
self.exact_host_filter = opts.exact_host_filter
|
|
101
|
-
|
|
102
|
-
self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx, threads=threads)
|
|
103
|
-
self._all_fqdns: Optional[list[str]] = None
|
|
104
|
-
self._id_devices: dict[int, models.NetboxDevice] = {}
|
|
105
|
-
self._name_devices: dict[str, models.NetboxDevice] = {}
|
|
106
|
-
self._short_name_devices: dict[str, models.NetboxDevice] = {}
|
|
107
|
-
|
|
108
|
-
def __enter__(self):
|
|
109
|
-
return self
|
|
110
|
-
|
|
111
|
-
def __exit__(self, _, __, ___):
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
def resolve_object_ids_by_query(self, query: NetboxQuery):
|
|
115
|
-
return [
|
|
116
|
-
d.id for d in self._load_devices(query)
|
|
117
|
-
]
|
|
118
|
-
|
|
119
|
-
def resolve_fdnds_by_query(self, query: NetboxQuery):
|
|
120
|
-
return [
|
|
121
|
-
d.name for d in self._load_devices(query)
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
def resolve_all_fdnds(self) -> list[str]:
|
|
125
|
-
if self._all_fqdns is None:
|
|
126
|
-
self._all_fqdns = [
|
|
127
|
-
d.name
|
|
128
|
-
for d in self.netbox.dcim_all_devices_brief().results
|
|
43
|
+
self.convert_ip_addresses = get_converter(
|
|
44
|
+
list[api_models.IpAddress],
|
|
45
|
+
list[IpAddressV37],
|
|
46
|
+
recipe=[
|
|
47
|
+
link_constant(P[IpAddressV37].prefix, value=None),
|
|
129
48
|
]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
query: Union[NetboxQuery, list],
|
|
135
|
-
preload_neighbors=False,
|
|
136
|
-
use_mesh=None,
|
|
137
|
-
preload_extra_fields=False,
|
|
138
|
-
**kwargs,
|
|
139
|
-
) -> List[models.NetboxDevice]:
|
|
140
|
-
if isinstance(query, list):
|
|
141
|
-
query = NetboxQuery.new(query)
|
|
142
|
-
|
|
143
|
-
devices = []
|
|
144
|
-
if query.is_host_query():
|
|
145
|
-
globs = []
|
|
146
|
-
for glob in query.globs:
|
|
147
|
-
if glob in self._name_devices:
|
|
148
|
-
devices.append(self._name_devices[glob])
|
|
149
|
-
if glob in self._short_name_devices:
|
|
150
|
-
devices.append(self._short_name_devices[glob])
|
|
151
|
-
else:
|
|
152
|
-
globs.append(glob)
|
|
153
|
-
if not globs:
|
|
154
|
-
return devices
|
|
155
|
-
query = NetboxQuery.new(globs)
|
|
156
|
-
|
|
157
|
-
return devices + self._make_devices(
|
|
158
|
-
query=query,
|
|
159
|
-
preload_neighbors=preload_neighbors,
|
|
160
|
-
use_mesh=use_mesh,
|
|
161
|
-
preload_extra_fields=preload_extra_fields,
|
|
162
|
-
**kwargs
|
|
49
|
+
)
|
|
50
|
+
self.convert_ip_prefixes = get_converter(
|
|
51
|
+
list[api_models.Prefix],
|
|
52
|
+
list[PrefixV37],
|
|
163
53
|
)
|
|
164
54
|
|
|
165
|
-
def
|
|
166
|
-
self,
|
|
167
|
-
query: NetboxQuery,
|
|
168
|
-
preload_neighbors=False,
|
|
169
|
-
use_mesh=None,
|
|
170
|
-
preload_extra_fields=False,
|
|
171
|
-
**kwargs,
|
|
172
|
-
) -> List[models.NetboxDevice]:
|
|
173
|
-
device_ids = {
|
|
174
|
-
device.id: extend_device(
|
|
175
|
-
device=device,
|
|
176
|
-
interfaces=[],
|
|
177
|
-
storage=self,
|
|
178
|
-
)
|
|
179
|
-
for device in self._load_devices(query)
|
|
180
|
-
}
|
|
181
|
-
if not device_ids:
|
|
182
|
-
return []
|
|
183
|
-
|
|
184
|
-
for device in device_ids.values():
|
|
185
|
-
self._record_device(device)
|
|
186
|
-
|
|
187
|
-
interfaces = self._load_interfaces(list(device_ids))
|
|
188
|
-
for interface in interfaces:
|
|
189
|
-
device_ids[interface.device.id].interfaces.append(interface)
|
|
190
|
-
|
|
191
|
-
return list(device_ids.values())
|
|
192
|
-
|
|
193
|
-
def _record_device(self, device: models.NetboxDevice):
|
|
194
|
-
self._id_devices[device.id] = device
|
|
195
|
-
self._short_name_devices[device.name] = device
|
|
196
|
-
if not self.exact_host_filter:
|
|
197
|
-
short_name = device.name.split(".")[0]
|
|
198
|
-
self._short_name_devices[short_name] = device
|
|
199
|
-
|
|
200
|
-
def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
|
|
201
|
-
if not query.globs:
|
|
202
|
-
return []
|
|
203
|
-
query_groups = parse_glob(self.exact_host_filter, query)
|
|
55
|
+
def list_all_fqdns(self) -> list[str]:
|
|
204
56
|
return [
|
|
205
|
-
|
|
206
|
-
for
|
|
207
|
-
if _match_query(self.exact_host_filter, query, device)
|
|
57
|
+
d.name
|
|
58
|
+
for d in self.netbox.dcim_all_devices_brief().results
|
|
208
59
|
]
|
|
209
60
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
for
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
ips = self.netbox.ipam_all_ip_addresses(interface_id=list(extended_ifaces))
|
|
217
|
-
ip_to_cidrs: Dict[str, str] = {ip.address: str(ip_interface(ip.address).network) for ip in ips.results}
|
|
218
|
-
prefixes = self.netbox.ipam_all_prefixes(prefix=list(ip_to_cidrs.values()))
|
|
219
|
-
cidr_to_prefix: Dict[str, models.Prefix] = {x.prefix: x for x in prefixes.results}
|
|
220
|
-
|
|
221
|
-
for ip in ips.results:
|
|
222
|
-
cidr = ip_to_cidrs[ip.address]
|
|
223
|
-
ip = extend_ip_address(ip, prefix=cidr_to_prefix.get(cidr))
|
|
224
|
-
extended_ifaces[ip.assigned_object_id].ip_addresses.append(ip)
|
|
225
|
-
return list(extended_ifaces.values())
|
|
226
|
-
|
|
227
|
-
def _load_interfaces(self, device_ids: List[int]) -> List[models.Interface]:
|
|
228
|
-
interfaces = self.netbox.dcim_all_interfaces(device_id=device_ids)
|
|
229
|
-
return self._extend_interfaces(interfaces.results)
|
|
230
|
-
|
|
231
|
-
def _load_interfaces_by_id(self, ids: List[int]) -> List[models.Interface]:
|
|
232
|
-
interfaces = self.netbox.dcim_all_interfaces_by_id(id=ids)
|
|
233
|
-
return self._extend_interfaces(interfaces.results)
|
|
234
|
-
|
|
235
|
-
def get_device(
|
|
236
|
-
self, obj_id, preload_neighbors=False, use_mesh=None,
|
|
237
|
-
**kwargs,
|
|
238
|
-
) -> models.NetboxDevice:
|
|
239
|
-
if obj_id in self._id_devices:
|
|
240
|
-
return self._id_devices[obj_id]
|
|
241
|
-
|
|
242
|
-
device = self.netbox.dcim_device(obj_id)
|
|
243
|
-
interfaces = self._load_interfaces([device.id])
|
|
244
|
-
|
|
245
|
-
res = extend_device(
|
|
246
|
-
device=device,
|
|
247
|
-
storage=self,
|
|
248
|
-
interfaces=interfaces,
|
|
249
|
-
)
|
|
250
|
-
self._record_device(res)
|
|
251
|
-
return res
|
|
252
|
-
|
|
253
|
-
def flush_perf(self):
|
|
254
|
-
pass
|
|
255
|
-
|
|
256
|
-
def search_connections(self, device: Device, neighbor: Device) -> list[tuple[Interface, Interface]]:
|
|
257
|
-
if device.storage is not self:
|
|
258
|
-
raise ValueError("device does not belong to this storage")
|
|
259
|
-
if neighbor.storage is not self:
|
|
260
|
-
raise ValueError("neighbor does not belong to this storage")
|
|
261
|
-
# both devices are NetboxDevice if they are loaded from this storage
|
|
262
|
-
res = []
|
|
263
|
-
for local_port in device.interfaces:
|
|
264
|
-
if not local_port.connected_endpoints:
|
|
265
|
-
continue
|
|
266
|
-
for endpoint in local_port.connected_endpoints:
|
|
267
|
-
if endpoint.device.id == neighbor.id:
|
|
268
|
-
for remote_port in neighbor.interfaces:
|
|
269
|
-
if remote_port.name == endpoint.name:
|
|
270
|
-
res.append((local_port, remote_port))
|
|
271
|
-
break
|
|
272
|
-
return res
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _match_query(exact_host_filter: bool, query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
276
|
-
"""
|
|
277
|
-
Additional filtering after netbox due to limited backend logic.
|
|
278
|
-
"""
|
|
279
|
-
if exact_host_filter:
|
|
280
|
-
return True # nothing to check, all filtering is done by netbox
|
|
281
|
-
hostnames = [subquery.strip() for subquery in query.globs if FIELD_VALUE_SEPARATOR not in subquery]
|
|
282
|
-
if not hostnames:
|
|
283
|
-
return True # no hostnames to check
|
|
61
|
+
def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceV37]:
|
|
62
|
+
return [
|
|
63
|
+
self.convert_device(dev)
|
|
64
|
+
for dev in self.netbox.dcim_all_devices(**query).results
|
|
65
|
+
]
|
|
284
66
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
hostname = hostname.strip().rstrip(".")
|
|
288
|
-
if short_name == hostname or device_data.name == hostname:
|
|
289
|
-
return True
|
|
290
|
-
return False
|
|
67
|
+
def get_device(self, device_id: int) -> NetboxDeviceV37:
|
|
68
|
+
return self.convert_device(self.netbox.dcim_device(device_id))
|
|
291
69
|
|
|
70
|
+
def list_interfaces_by_devices(self, device_ids: list[int]) -> list[InterfaceV37]:
|
|
71
|
+
return self.convert_interfaces(self.netbox.dcim_all_interfaces(device_id=device_ids).results)
|
|
292
72
|
|
|
293
|
-
def
|
|
294
|
-
|
|
295
|
-
# ie find "host" with fqdn "host.example.com"
|
|
296
|
-
# besides using name__ic (ie startswith)
|
|
297
|
-
# since there is no direct analogue for this field in netbox
|
|
298
|
-
# so we need to add a dot to hostnames (top-level fqdn part)
|
|
299
|
-
# so we would not receive devices with a common name prefix
|
|
300
|
-
def add_dot(raw_query: Any) -> Any:
|
|
301
|
-
if isinstance(raw_query, str) and "." not in raw_query:
|
|
302
|
-
raw_query = raw_query + "."
|
|
303
|
-
return raw_query
|
|
73
|
+
def list_interfaces(self, ids: list[int]) -> list[InterfaceV37]:
|
|
74
|
+
return self.convert_interfaces(self.netbox.dcim_all_interfaces(id=ids).results)
|
|
304
75
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
raw_query[i] = add_dot(name)
|
|
308
|
-
elif isinstance(raw_query, str):
|
|
309
|
-
raw_query = add_dot(raw_query)
|
|
76
|
+
def list_ipaddr_by_ifaces(self, iface_ids: list[int]) -> list[IpAddressV37]:
|
|
77
|
+
return self.convert_ip_addresses(self.netbox.ipam_all_ip_addresses(interface_id=iface_ids).results)
|
|
310
78
|
|
|
311
|
-
|
|
79
|
+
def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixV37]:
|
|
80
|
+
return self.convert_ip_prefixes(self.netbox.ipam_all_prefixes(prefix=prefixes).results)
|
|
312
81
|
|
|
313
82
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
83
|
+
class NetboxStorageV37(BaseNetboxStorage[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]):
|
|
84
|
+
def _init_adapter(
|
|
85
|
+
self,
|
|
86
|
+
url: str,
|
|
87
|
+
token: str,
|
|
88
|
+
ssl_context: ssl.SSLContext | None,
|
|
89
|
+
threads: int,
|
|
90
|
+
) -> NetboxAdapter[NetboxDeviceV37, InterfaceV37, IpAddressV37, PrefixV37]:
|
|
91
|
+
return NetboxV37Adapter(self, url, token, ssl_context, threads)
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import warnings
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from annet.adapters.netbox.common.models import Entity, Interface, InterfaceType, IpAddress, Label, NetboxDevice, DeviceIp, IpFamily, Prefix
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class PrefixV41(Prefix):
|
|
10
|
+
site: Optional[Entity] = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class IpAddressV41(IpAddress[PrefixV41]):
|
|
15
|
+
prefix: Optional[PrefixV41] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class InterfaceV41(Interface[IpAddressV41]):
|
|
20
|
+
def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None:
|
|
21
|
+
self.ip_addresses.append(IpAddressV41(
|
|
22
|
+
id=0,
|
|
23
|
+
display=address_mask,
|
|
24
|
+
address=address_mask,
|
|
25
|
+
vrf=vrf,
|
|
26
|
+
prefix=None,
|
|
27
|
+
family=family,
|
|
28
|
+
created=datetime.now(timezone.utc),
|
|
29
|
+
last_updated=datetime.now(timezone.utc),
|
|
30
|
+
tags=[],
|
|
31
|
+
status=Label(value="active", label="Active"),
|
|
32
|
+
assigned_object_id=self.id,
|
|
33
|
+
))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@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]):
|
|
46
|
+
role: Entity
|
|
47
|
+
primary_ip: Optional[DeviceIpV41]
|
|
48
|
+
primary_ip4: Optional[DeviceIpV41]
|
|
49
|
+
primary_ip6: Optional[DeviceIpV41]
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def device_role(self):
|
|
53
|
+
warnings.warn(
|
|
54
|
+
"'device_role' is deprecated, use 'role' instead.",
|
|
55
|
+
DeprecationWarning,
|
|
56
|
+
stacklevel=2
|
|
57
|
+
)
|
|
58
|
+
return self.role
|
|
59
|
+
|
|
60
|
+
def __hash__(self):
|
|
61
|
+
return hash((self.id, type(self)))
|
|
62
|
+
|
|
63
|
+
def _make_interface(self, name: str, type: InterfaceType) -> InterfaceV41:
|
|
64
|
+
return InterfaceV41(
|
|
65
|
+
name=name,
|
|
66
|
+
device=self,
|
|
67
|
+
enabled=True,
|
|
68
|
+
description="",
|
|
69
|
+
type=type,
|
|
70
|
+
id=0,
|
|
71
|
+
vrf=None,
|
|
72
|
+
display=name,
|
|
73
|
+
untagged_vlan=None,
|
|
74
|
+
tagged_vlans=[],
|
|
75
|
+
ip_addresses=[],
|
|
76
|
+
connected_endpoints=[],
|
|
77
|
+
mode=None,
|
|
78
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
from adaptix import P
|
|
3
|
+
from adaptix.conversion import get_converter, link, link_constant, link_function
|
|
4
|
+
from annetbox.v41 import client_sync
|
|
5
|
+
from annetbox.v41 import models as api_models
|
|
6
|
+
|
|
7
|
+
from annet.adapters.netbox.common.adapter import NetboxAdapter, get_device_breed, get_device_hw
|
|
8
|
+
from annet.adapters.netbox.common.storage_base import BaseNetboxStorage
|
|
9
|
+
from annet.adapters.netbox.v41.models import InterfaceV41, IpAddressV41, NetboxDeviceV41, PrefixV41
|
|
10
|
+
from annet.storage import Storage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NetboxV41Adapter(NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
storage: Storage,
|
|
17
|
+
url: str,
|
|
18
|
+
token: str,
|
|
19
|
+
ssl_context: ssl.SSLContext | None,
|
|
20
|
+
threads: int,
|
|
21
|
+
):
|
|
22
|
+
self.netbox = client_sync.NetboxV41(url=url, token=token, ssl_context=ssl_context, threads=threads)
|
|
23
|
+
self.convert_device = get_converter(
|
|
24
|
+
api_models.Device,
|
|
25
|
+
NetboxDeviceV41,
|
|
26
|
+
recipe=[
|
|
27
|
+
link_function(get_device_breed, P[NetboxDeviceV41].breed),
|
|
28
|
+
link_function(get_device_hw, P[NetboxDeviceV41].hw),
|
|
29
|
+
link_constant(P[NetboxDeviceV41].interfaces, factory=list),
|
|
30
|
+
link_constant(P[NetboxDeviceV41].storage, value=storage),
|
|
31
|
+
link(P[api_models.Device].name, P[NetboxDeviceV41].hostname),
|
|
32
|
+
link(P[api_models.Device].name, P[NetboxDeviceV41].fqdn),
|
|
33
|
+
]
|
|
34
|
+
)
|
|
35
|
+
self.convert_interfaces = get_converter(
|
|
36
|
+
list[api_models.Interface],
|
|
37
|
+
list[InterfaceV41],
|
|
38
|
+
recipe=[
|
|
39
|
+
link_constant(P[InterfaceV41].ip_addresses, factory=list),
|
|
40
|
+
link_constant(P[InterfaceV41].lag_min_links, value=None),
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
self.convert_ip_addresses = get_converter(
|
|
44
|
+
list[api_models.IpAddress],
|
|
45
|
+
list[IpAddressV41],
|
|
46
|
+
recipe=[
|
|
47
|
+
link_constant(P[IpAddressV41].prefix, value=None),
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
self.convert_ip_prefixes = get_converter(
|
|
51
|
+
list[api_models.Prefix],
|
|
52
|
+
list[PrefixV41],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def list_all_fqdns(self) -> list[str]:
|
|
56
|
+
return [
|
|
57
|
+
d.name
|
|
58
|
+
for d in self.netbox.dcim_all_devices_brief().results
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceV41]:
|
|
62
|
+
return [
|
|
63
|
+
self.convert_device(dev)
|
|
64
|
+
for dev in self.netbox.dcim_all_devices(**query).results
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
def get_device(self, device_id: int) -> NetboxDeviceV41:
|
|
68
|
+
return self.convert_device(self.netbox.dcim_device(device_id))
|
|
69
|
+
|
|
70
|
+
def list_interfaces_by_devices(self, device_ids: list[int]) -> list[InterfaceV41]:
|
|
71
|
+
return self.convert_interfaces(self.netbox.dcim_all_interfaces(device_id=device_ids).results)
|
|
72
|
+
|
|
73
|
+
def list_interfaces(self, ids: list[int]) -> list[InterfaceV41]:
|
|
74
|
+
return self.convert_interfaces(self.netbox.dcim_all_interfaces(id=ids).results)
|
|
75
|
+
|
|
76
|
+
def list_ipaddr_by_ifaces(self, iface_ids: list[int]) -> list[IpAddressV41]:
|
|
77
|
+
return self.convert_ip_addresses(self.netbox.ipam_all_ip_addresses(interface_id=iface_ids).results)
|
|
78
|
+
|
|
79
|
+
def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixV41]:
|
|
80
|
+
return self.convert_ip_prefixes(self.netbox.ipam_all_prefixes(prefix=prefixes).results)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class NetboxStorageV41(BaseNetboxStorage[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]):
|
|
84
|
+
def _init_adapter(
|
|
85
|
+
self,
|
|
86
|
+
url: str,
|
|
87
|
+
token: str,
|
|
88
|
+
ssl_context: ssl.SSLContext | None,
|
|
89
|
+
threads: int,
|
|
90
|
+
) -> NetboxAdapter[NetboxDeviceV41, InterfaceV41, IpAddressV41, PrefixV41]:
|
|
91
|
+
return NetboxV41Adapter(self, url, token, ssl_context, threads)
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone
|
|
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
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class PrefixV42(Prefix):
|
|
10
|
+
scope: Optional[Entity] = None
|
|
11
|
+
scope_type: str | None = None
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def site(self) -> Optional[Entity]:
|
|
15
|
+
if self.scope_type == "dcim.site":
|
|
16
|
+
return self.scope
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class IpAddressV42(IpAddressV41):
|
|
22
|
+
prefix: Optional[PrefixV42] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class InterfaceV42(InterfaceV41):
|
|
27
|
+
def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily) -> None:
|
|
28
|
+
self.ip_addresses.append(IpAddressV42(
|
|
29
|
+
id=0,
|
|
30
|
+
display=address_mask,
|
|
31
|
+
address=address_mask,
|
|
32
|
+
vrf=vrf,
|
|
33
|
+
prefix=None,
|
|
34
|
+
family=family,
|
|
35
|
+
created=datetime.now(timezone.utc),
|
|
36
|
+
last_updated=datetime.now(timezone.utc),
|
|
37
|
+
tags=[],
|
|
38
|
+
status=Label(value="active", label="Active"),
|
|
39
|
+
assigned_object_id=self.id,
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class NetboxDeviceV42(NetboxDeviceV41):
|
|
45
|
+
def __hash__(self):
|
|
46
|
+
return hash((self.id, type(self)))
|
|
47
|
+
|
|
48
|
+
def _make_interface(self, name: str, type: InterfaceType) -> InterfaceV42:
|
|
49
|
+
return InterfaceV42(
|
|
50
|
+
name=name,
|
|
51
|
+
device=self,
|
|
52
|
+
enabled=True,
|
|
53
|
+
description="",
|
|
54
|
+
type=type,
|
|
55
|
+
id=0,
|
|
56
|
+
vrf=None,
|
|
57
|
+
display=name,
|
|
58
|
+
untagged_vlan=None,
|
|
59
|
+
tagged_vlans=[],
|
|
60
|
+
ip_addresses=[],
|
|
61
|
+
connected_endpoints=[],
|
|
62
|
+
mode=None,
|
|
63
|
+
)
|