nbforager 0.2.6__tar.gz → 0.4.1__tar.gz
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.
- {nbforager-0.2.6 → nbforager-0.4.1}/PKG-INFO +17 -4
- {nbforager-0.2.6 → nbforager-0.4.1}/README.rst +13 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/base_c.py +13 -8
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/connector.py +14 -12
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/forager.py +65 -43
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/joiner.py +67 -72
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/helpers.py +209 -27
- nbforager-0.4.1/nbforager/nb_api.py +367 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_forager.py +23 -8
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_tree.py +45 -2
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_custom.py +1 -1
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_parser.py +7 -13
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/types_.py +4 -1
- {nbforager-0.2.6 → nbforager-0.4.1}/pyproject.toml +18 -14
- nbforager-0.2.6/nbforager/models/__init__.py +0 -1
- nbforager-0.2.6/nbforager/models/dcim/__init__.py +0 -1
- nbforager-0.2.6/nbforager/models/dcim/interfaces/__init__.py +0 -1
- nbforager-0.2.6/nbforager/models/dcim/interfaces/interface_nb.py +0 -13
- nbforager-0.2.6/nbforager/nb_api.py +0 -187
- /nbforager-0.2.6/LICENCE.txt → /nbforager-0.4.1/LICENSE.txt +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/__init__.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/__init__.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/base_ca.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/circuits.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/core.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/dcim.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/extended_get.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/extras.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/ip_addresses.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/ipam.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/plugins_ca.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/status.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/tenancy.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/users.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/virtualization.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/wireless.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/exceptions.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/__init__.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/base_fa.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/circuits.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/core.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/dcim.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/extras.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/ipam.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/ipv4.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/tenancy.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/users.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/virtualization.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/wireless.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/log.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/messages.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_cache.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/__init__.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_value.py +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/py.typed +0 -0
- {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/py_tree.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nbforager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
4
4
|
Summary: Python package designed to assist in working with the Netbox REST API. The filter parameters are identical to those in the Web UI filter form. It replaces brief data with full information, and Netbox objects are represented as a recursive multidimensional dictionary.
|
5
5
|
Home-page: https://github.com/vladimirs-git/nbforager
|
6
6
|
License: Apache-2.0
|
@@ -20,18 +20,25 @@ Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
21
21
|
Provides-Extra: test
|
22
22
|
Requires-Dist: ciscoconfparse (>=1.9,<2.0)
|
23
|
-
Requires-Dist: netports (>=0.
|
23
|
+
Requires-Dist: netports (>=0.14,<1.0.0)
|
24
24
|
Requires-Dist: pydantic (>=2,<3)
|
25
25
|
Requires-Dist: pynetbox (>=7.3.3,<8.0.0)
|
26
26
|
Requires-Dist: requests (>=2,<3)
|
27
27
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
28
28
|
Requires-Dist: tomli (==2.0.1)
|
29
|
-
Requires-Dist: vhelpers (>=0.
|
29
|
+
Requires-Dist: vhelpers (>=0.3,<1.0.0)
|
30
30
|
Project-URL: Bug Tracker, https://github.com/vladimirs-git/nbforager/issues
|
31
|
-
Project-URL: Download URL, https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.
|
31
|
+
Project-URL: Download URL, https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.4.1.tar.gz
|
32
32
|
Project-URL: Repository, https://github.com/vladimirs-git/nbforager
|
33
33
|
Description-Content-Type: text/x-rst
|
34
34
|
|
35
|
+
|
36
|
+
.. image:: https://img.shields.io/pypi/v/netports.svg
|
37
|
+
:target: https://pypi.python.org/pypi/netports
|
38
|
+
.. image:: https://img.shields.io/badge/Python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg
|
39
|
+
:target: https://pypi.python.org/pypi/logger-color
|
40
|
+
|
41
|
+
|
35
42
|
nbforager
|
36
43
|
=========
|
37
44
|
|
@@ -51,6 +58,12 @@ Fully documented on `Read the Docs`_.
|
|
51
58
|
|
52
59
|
----------------------------------------------------------------------------------------
|
53
60
|
|
61
|
+
Requirements
|
62
|
+
============
|
63
|
+
|
64
|
+
Python >=3.8,<3.12
|
65
|
+
|
66
|
+
|
54
67
|
Quickstart
|
55
68
|
==========
|
56
69
|
|
@@ -1,3 +1,10 @@
|
|
1
|
+
|
2
|
+
.. image:: https://img.shields.io/pypi/v/netports.svg
|
3
|
+
:target: https://pypi.python.org/pypi/netports
|
4
|
+
.. image:: https://img.shields.io/badge/Python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg
|
5
|
+
:target: https://pypi.python.org/pypi/logger-color
|
6
|
+
|
7
|
+
|
1
8
|
nbforager
|
2
9
|
=========
|
3
10
|
|
@@ -17,6 +24,12 @@ Fully documented on `Read the Docs`_.
|
|
17
24
|
|
18
25
|
----------------------------------------------------------------------------------------
|
19
26
|
|
27
|
+
Requirements
|
28
|
+
============
|
29
|
+
|
30
|
+
Python >=3.8,<3.12
|
31
|
+
|
32
|
+
|
20
33
|
Quickstart
|
21
34
|
==========
|
22
35
|
|
@@ -157,9 +157,10 @@ class BaseC:
|
|
157
157
|
session timeout reached. Default is `10`.
|
158
158
|
|
159
159
|
:param bool strict: When querying objects by tag, if there are no tags present,
|
160
|
-
the Netbox API response returns a status_code=400.
|
161
|
-
raised when status_code=400.
|
162
|
-
empty list is returned with status_code=200.
|
160
|
+
the Netbox API response returns a status_code=400.
|
161
|
+
True - ConnectionError is raised when status_code=400.
|
162
|
+
False - WARNING message is logged and an empty list is returned with status_code=200.
|
163
|
+
Default is `False`.
|
163
164
|
|
164
165
|
:param bool extended_get: True - Extend filtering parameters in GET request,
|
165
166
|
``{parameter}`` can be used instead of ``{parameter}_id``. Default is `True`.
|
@@ -180,9 +181,9 @@ class BaseC:
|
|
180
181
|
self.threads: int = _init_threads(**kwargs)
|
181
182
|
self.interval: float = float(kwargs.get("interval") or 0.0)
|
182
183
|
# Errors processing
|
183
|
-
self.timeout:
|
184
|
+
self.timeout: int = int(kwargs.get("timeout") or 60)
|
184
185
|
self.max_retries: int = int(kwargs.get("max_retries") or 0)
|
185
|
-
self.sleep:
|
186
|
+
self.sleep: int = int(kwargs.get("sleep") or 10)
|
186
187
|
self.strict: bool = bool(kwargs.get("strict"))
|
187
188
|
# Settings
|
188
189
|
self.extended_get: bool = bool(kwargs.get("extended_get"))
|
@@ -316,6 +317,9 @@ class BaseC:
|
|
316
317
|
:return: Netbox objects. Update self _results.
|
317
318
|
"""
|
318
319
|
offset = 0
|
320
|
+
if offsets := params_d.get("offset"):
|
321
|
+
offset = int(offsets[0])
|
322
|
+
|
319
323
|
max_limit: int = self._set_limit(params_d)
|
320
324
|
params_l: LParam = vparam.from_dict(params_d)
|
321
325
|
|
@@ -351,12 +355,12 @@ class BaseC:
|
|
351
355
|
:return: Max limit value, update params_d["limit"] value
|
352
356
|
"""
|
353
357
|
limit = 0
|
354
|
-
if limit_ := vdict.pop("limit"
|
358
|
+
if limit_ := vdict.pop(params_d, key="limit") or []:
|
355
359
|
limit = int(limit_[0])
|
356
360
|
if not limit:
|
357
361
|
limit = self.limit
|
358
362
|
max_limit = 0
|
359
|
-
if max_limit_ := vdict.pop("max_limit"
|
363
|
+
if max_limit_ := vdict.pop(params_d, key="max_limit") or []:
|
360
364
|
max_limit = int(max_limit_[0])
|
361
365
|
if max_limit and max_limit < limit:
|
362
366
|
limit = max_limit
|
@@ -485,7 +489,7 @@ class BaseC:
|
|
485
489
|
url=url,
|
486
490
|
headers=self._headers(),
|
487
491
|
verify=self.verify,
|
488
|
-
timeout=self.timeout,
|
492
|
+
timeout=float(self.timeout),
|
489
493
|
)
|
490
494
|
except ReadTimeout:
|
491
495
|
attempts = f"{counter} of {self.max_retries}"
|
@@ -501,6 +505,7 @@ class BaseC:
|
|
501
505
|
|
502
506
|
if response.ok:
|
503
507
|
return response
|
508
|
+
|
504
509
|
msg = self._msg_status_code(response)
|
505
510
|
if self._is_status_code_5xx(response):
|
506
511
|
raise ConnectionError(f"Netbox server error: {msg}.")
|
@@ -23,9 +23,9 @@ class Connector(BaseC):
|
|
23
23
|
# ============================= methods ==============================
|
24
24
|
|
25
25
|
def create(self, **kwargs) -> Response:
|
26
|
-
"""Create object in Netbox.
|
26
|
+
"""Create an object in Netbox.
|
27
27
|
|
28
|
-
:param kwargs: Parameters
|
28
|
+
:param kwargs: Parameters for creating a new object.
|
29
29
|
|
30
30
|
:return: Session response.
|
31
31
|
|
@@ -43,11 +43,11 @@ class Connector(BaseC):
|
|
43
43
|
return response
|
44
44
|
|
45
45
|
def create_d(self, **kwargs) -> DAny:
|
46
|
-
"""Create object in Netbox.
|
46
|
+
"""Create an object in Netbox.
|
47
47
|
|
48
|
-
:param kwargs:
|
48
|
+
:param kwargs: Parameters for creating a new object.
|
49
49
|
|
50
|
-
:return: Data of newly
|
50
|
+
:return: Data of newly created object.
|
51
51
|
:rtype: dict
|
52
52
|
"""
|
53
53
|
response: Response = self.create(**kwargs)
|
@@ -59,7 +59,7 @@ class Connector(BaseC):
|
|
59
59
|
|
60
60
|
# noinspection PyShadowingBuiltins
|
61
61
|
def delete(self, id: int) -> Response: # pylint: disable=redefined-builtin
|
62
|
-
"""Delete object in Netbox.
|
62
|
+
"""Delete an object in Netbox.
|
63
63
|
|
64
64
|
:param id: Object ID.
|
65
65
|
:type id: int
|
@@ -70,6 +70,8 @@ class Connector(BaseC):
|
|
70
70
|
- <Response [404]> Object not found.
|
71
71
|
:rtype: Response
|
72
72
|
"""
|
73
|
+
if not id:
|
74
|
+
raise ValueError("id is required.")
|
73
75
|
response: Response = self._session.delete(
|
74
76
|
url=f"{self.url}{id}",
|
75
77
|
headers=self._headers(),
|
@@ -123,12 +125,12 @@ class Connector(BaseC):
|
|
123
125
|
|
124
126
|
# noinspection PyIncorrectDocstring
|
125
127
|
def update(self, **kwargs) -> Response:
|
126
|
-
"""Update object in Netbox.
|
128
|
+
"""Update an object in Netbox.
|
127
129
|
|
128
130
|
:param id: Netbox object id to update.
|
129
131
|
:type id: int
|
130
132
|
|
131
|
-
:param kwargs: Parameters to update object in Netbox.
|
133
|
+
:param kwargs: Parameters to update an object in Netbox.
|
132
134
|
|
133
135
|
:return: Session response.
|
134
136
|
|
@@ -136,9 +138,9 @@ class Connector(BaseC):
|
|
136
138
|
- <Response [400]> Invalid data.
|
137
139
|
:rtype: Response
|
138
140
|
"""
|
139
|
-
id_ = vdict.pop("id"
|
141
|
+
id_ = vdict.pop(kwargs, key="id")
|
140
142
|
if not id_:
|
141
|
-
raise ValueError("id
|
143
|
+
raise ValueError("id is required in the data.")
|
142
144
|
|
143
145
|
response: Response = self._session.patch(
|
144
146
|
url=f"{self.url}{id_}/",
|
@@ -151,12 +153,12 @@ class Connector(BaseC):
|
|
151
153
|
|
152
154
|
# noinspection PyIncorrectDocstring
|
153
155
|
def update_d(self, **kwargs) -> DAny:
|
154
|
-
"""Update object in Netbox.
|
156
|
+
"""Update an object in Netbox.
|
155
157
|
|
156
158
|
:param id: Netbox object id to update.
|
157
159
|
:type id: int
|
158
160
|
|
159
|
-
:param kwargs: Parameters to update object in Netbox.
|
161
|
+
:param kwargs: Parameters to update an object in Netbox.
|
160
162
|
|
161
163
|
:return: Data of updated object.
|
162
164
|
:rtype: dict
|
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
|
-
import logging
|
8
7
|
import time
|
9
8
|
from queue import Queue
|
10
9
|
from threading import Thread
|
@@ -13,11 +12,12 @@ from urllib.parse import urlparse, parse_qs
|
|
13
12
|
from vhelpers import vstr
|
14
13
|
|
15
14
|
from nbforager import helpers as h
|
15
|
+
from nbforager import nb_tree
|
16
16
|
from nbforager.nb_api import NbApi
|
17
|
-
from nbforager.nb_tree import NbTree
|
18
|
-
from nbforager.parser
|
17
|
+
from nbforager.nb_tree import NbTree
|
18
|
+
from nbforager.parser import nb_parser
|
19
19
|
from nbforager.py_tree import PyTree
|
20
|
-
from nbforager.types_ import LDAny, DiDAny, LStr, LT2StrDAny, DList, LDList, DiAny
|
20
|
+
from nbforager.types_ import LDAny, DiDAny, LStr, LT2StrDAny, DList, LDList, DiAny, SInt, DAny
|
21
21
|
|
22
22
|
|
23
23
|
class Forager:
|
@@ -86,7 +86,10 @@ class Forager:
|
|
86
86
|
:return: None. Update self object.
|
87
87
|
"""
|
88
88
|
# Query main data
|
89
|
-
|
89
|
+
kwargs_: DAny = self._delete_existing_nested_ids(**kwargs)
|
90
|
+
if kwargs and not kwargs_:
|
91
|
+
return
|
92
|
+
nb_objects: LDAny = self._get_root_data_from_netbox(nested=nested, **kwargs_)
|
90
93
|
if not nested:
|
91
94
|
return
|
92
95
|
urls: LStr = self._collect_nested_urls(nb_objects)
|
@@ -104,7 +107,7 @@ class Forager:
|
|
104
107
|
# loop
|
105
108
|
else:
|
106
109
|
for url in urls:
|
107
|
-
app, model, _ = h.
|
110
|
+
app, model, _ = h.url_to_ami_items(url)
|
108
111
|
path = f"{app}/{model}/"
|
109
112
|
connector = self._get_connector(path)
|
110
113
|
params_d: DList = parse_qs(urlparse(url).query)
|
@@ -134,7 +137,7 @@ class Forager:
|
|
134
137
|
|
135
138
|
:return: Filtered Netbox objects.
|
136
139
|
"""
|
137
|
-
return find_objects(objects=list(self.root_d.values()), **kwargs)
|
140
|
+
return nb_parser.find_objects(objects=list(self.root_d.values()), **kwargs)
|
138
141
|
|
139
142
|
def find_rse(self, role: str = "", site: str = "", env: str = "", **kwargs) -> LDAny:
|
140
143
|
"""Find Netbox objects in NbForager.tree by Role-Sile-Env finding parameters.
|
@@ -155,7 +158,7 @@ class Forager:
|
|
155
158
|
}
|
156
159
|
params = {k: v for k, v in params.items() if v}
|
157
160
|
kwargs.update(params)
|
158
|
-
return find_objects(objects=list(self.tree_d.values()), **kwargs)
|
161
|
+
return nb_parser.find_objects(objects=list(self.tree_d.values()), **kwargs)
|
159
162
|
|
160
163
|
def find_tree(self, **kwargs) -> LDAny:
|
161
164
|
"""Find Netbox objects in NbForager.tree by extended finding parameters.
|
@@ -167,48 +170,50 @@ class Forager:
|
|
167
170
|
|
168
171
|
:return: Filtered Netbox objects.
|
169
172
|
"""
|
170
|
-
return find_objects(objects=list(self.tree_d.values()), **kwargs)
|
173
|
+
return nb_parser.find_objects(objects=list(self.tree_d.values()), **kwargs)
|
171
174
|
|
172
175
|
# ============================= helpers ==============================
|
173
176
|
|
174
|
-
def
|
177
|
+
def _delete_existing_nested_ids(self, **kwargs) -> DAny:
|
178
|
+
"""Delete the IDs of objects that are already present in the tree and nested=True.
|
179
|
+
|
180
|
+
Delete only if kwargs["id"] is a list, ignore other data types.
|
181
|
+
|
182
|
+
:param kwargs: Filtering parameters.
|
183
|
+
|
184
|
+
:return: None. Update IDs in kwargs.
|
185
|
+
"""
|
186
|
+
if list(kwargs) != ["id"]:
|
187
|
+
return kwargs
|
188
|
+
if not isinstance(kwargs["id"], list):
|
189
|
+
return kwargs
|
190
|
+
ids: SInt = set(kwargs["id"])
|
191
|
+
existing_ids: SInt = {d["id"] for d in self.find_root(_nested=True, **kwargs)}
|
192
|
+
if new_ids := sorted(set(ids).difference(existing_ids)):
|
193
|
+
return {"id": new_ids}
|
194
|
+
return {}
|
195
|
+
|
196
|
+
def _get_root_data_from_netbox(self, nested: bool = False, **kwargs) -> LDAny:
|
175
197
|
"""Retrieve data from the Netbox.
|
176
198
|
|
177
199
|
Request data based on the kwargs filter parameters and
|
178
200
|
save the received objects to the NbForager.root.
|
201
|
+
Set extra `_nested` value in Netbox object.
|
179
202
|
|
180
|
-
:param
|
203
|
+
:param bool nested: `True` - Request base and nested objects,
|
204
|
+
`False` - Request only base objects. Default id `False`
|
205
|
+
|
206
|
+
:param kwargs: Filtering parameters.
|
181
207
|
|
182
208
|
:return: List of Netbox objects. Update NbForager.root object.
|
183
209
|
"""
|
184
210
|
nb_objects: LDAny = self.connector.get(**kwargs)
|
185
|
-
nb_objects = self._validate_ids(nb_objects)
|
186
211
|
for nb_object in nb_objects:
|
187
|
-
|
212
|
+
nb_object["_nested"] = nested
|
213
|
+
idx = int(nb_object["id"])
|
214
|
+
self.root_d[idx] = nb_object
|
188
215
|
return nb_objects
|
189
216
|
|
190
|
-
@staticmethod
|
191
|
-
def _validate_ids(nb_objects: LDAny) -> LDAny:
|
192
|
-
"""Check the IDs in the items. The ID should be a unique integer.
|
193
|
-
|
194
|
-
:param nb_objects: List of dictionaries containing Netbox objects.
|
195
|
-
|
196
|
-
:return: None. Logging an error if the ID does not match the conditions.
|
197
|
-
"""
|
198
|
-
nb_objects_: LDAny = []
|
199
|
-
for nb_object in nb_objects:
|
200
|
-
id_ = nb_object.get("id")
|
201
|
-
if not isinstance(id_, int):
|
202
|
-
msg = f"TypeError: {id_=} {int} expected in {nb_object=}."
|
203
|
-
logging.error(msg)
|
204
|
-
continue
|
205
|
-
if id_ in nb_objects:
|
206
|
-
msg = f"ValueError: Duplicate {id_=} in nb_objects_ and {nb_object=}."
|
207
|
-
logging.error(msg)
|
208
|
-
continue
|
209
|
-
nb_objects_.append(nb_object)
|
210
|
-
return nb_objects_
|
211
|
-
|
212
217
|
def _collect_nested_urls(self, nb_objects: LDAny) -> LStr:
|
213
218
|
"""Collect nested urls.
|
214
219
|
|
@@ -217,10 +222,20 @@ class Forager:
|
|
217
222
|
:return: Nested URLs.
|
218
223
|
"""
|
219
224
|
urls: LStr = h.nested_urls(nb_objects)
|
220
|
-
urls = missed_urls(urls=urls, tree=self.root)
|
225
|
+
urls = nb_tree.missed_urls(urls=urls, tree=self.root)
|
221
226
|
urls = h.join_urls(urls)
|
222
|
-
urls = [s for s in urls if h.
|
223
|
-
|
227
|
+
urls = [s for s in urls if h.url_to_ami_items(s)[0]]
|
228
|
+
|
229
|
+
urls_: LStr = []
|
230
|
+
|
231
|
+
for url in urls:
|
232
|
+
app, model, _ = h.url_to_ami_items(url)
|
233
|
+
path = f"{app}/{model}/"
|
234
|
+
connector = self._get_connector(path)
|
235
|
+
urls_sliced = h.slice_url(url, max_len=connector.url_length)
|
236
|
+
urls_.extend(urls_sliced)
|
237
|
+
|
238
|
+
return urls_
|
224
239
|
|
225
240
|
# noinspection PyProtectedMember
|
226
241
|
def _get_path_params(self, urls: LStr) -> LT2StrDAny:
|
@@ -232,7 +247,7 @@ class Forager:
|
|
232
247
|
"""
|
233
248
|
path_params: LT2StrDAny = []
|
234
249
|
for url in urls:
|
235
|
-
app, model, _ = h.
|
250
|
+
app, model, _ = h.url_to_ami_items(url)
|
236
251
|
path = f"{app}/{model}/"
|
237
252
|
connector = self._get_connector(path)
|
238
253
|
params_d = parse_qs(urlparse(url).query)
|
@@ -310,13 +325,20 @@ class Forager:
|
|
310
325
|
connector = getattr(getattr(self.api, app), model)
|
311
326
|
return connector
|
312
327
|
|
313
|
-
def _save_results(self, results):
|
314
|
-
|
328
|
+
def _save_results(self, results: LDAny) -> None:
|
329
|
+
"""Save Netbox objects to root NbTree object.
|
330
|
+
|
331
|
+
:param results: Data to be saved.
|
332
|
+
:return: None. root NbTree object.
|
333
|
+
"""
|
315
334
|
for data in results:
|
316
|
-
app, model,
|
335
|
+
app, model, idx_ = h.url_to_ami_items(data["url"])
|
336
|
+
idx = int(idx_)
|
317
337
|
path = f"{app}/{model}"
|
318
338
|
model_d: DiDAny = self._get_root_data(path)
|
319
|
-
|
339
|
+
if idx not in model_d:
|
340
|
+
data["_nested"] = False
|
341
|
+
model_d[idx] = data
|
320
342
|
|
321
343
|
def _get_pynb_data(self, path: str) -> DiAny:
|
322
344
|
"""Get data in self pynb by app/model path.
|
@@ -1,16 +1,15 @@
|
|
1
1
|
"""Joiner."""
|
2
2
|
from operator import itemgetter
|
3
3
|
|
4
|
-
from netports import Intf
|
5
4
|
from vhelpers import vlist
|
6
5
|
|
7
6
|
from nbforager import helpers as h
|
8
7
|
from nbforager.api.base_c import BaseC
|
9
|
-
from nbforager.foragers.forager import find_objects
|
10
8
|
from nbforager.foragers.ipv4 import IPv4
|
11
9
|
from nbforager.nb_tree import NbTree
|
10
|
+
from nbforager.parser import nb_parser
|
12
11
|
from nbforager.parser.nb_value import NbValue
|
13
|
-
from nbforager.types_ import LDAny, DAny, LStr, DiDAny, LInt, DiLDAny
|
12
|
+
from nbforager.types_ import LDAny, DAny, LStr, DiDAny, LInt, DiLDAny, SInt
|
14
13
|
|
15
14
|
|
16
15
|
class Joiner:
|
@@ -62,7 +61,7 @@ class Joiner:
|
|
62
61
|
data["_sub_prefixes"] = [] # LDAny
|
63
62
|
data["_ip_addresses"] = [] # LDAny
|
64
63
|
|
65
|
-
def join_dcim_devices(self
|
64
|
+
def join_dcim_devices(self) -> None:
|
66
65
|
"""Create additional keys to represent dcim.devices similar to the WEB UI.
|
67
66
|
|
68
67
|
In dcim.devices:
|
@@ -83,39 +82,33 @@ class Joiner:
|
|
83
82
|
|
84
83
|
- ``_ip_addresses``
|
85
84
|
|
86
|
-
:param kwargs: Filtering parameters.
|
87
|
-
|
88
85
|
:return: None. Update NbTree object.
|
89
86
|
"""
|
90
|
-
self._join_virtual_chassis(
|
91
|
-
intf_ids: LInt = self._join_dcim_devices(
|
92
|
-
self.
|
87
|
+
self._join_virtual_chassis()
|
88
|
+
intf_ids: LInt = self._join_dcim_devices()
|
89
|
+
self._join_ip_addresses(intf_ids, app="dcim")
|
93
90
|
|
94
|
-
def _join_virtual_chassis(self
|
91
|
+
def _join_virtual_chassis(self):
|
95
92
|
"""Add virtual-chassis members to master devices.
|
96
93
|
|
97
|
-
:param kwargs: Additional keyword arguments to filter devices.
|
98
94
|
:return: None. Update data in object.
|
99
95
|
"""
|
100
96
|
app = "dcim"
|
101
97
|
model = "devices"
|
102
|
-
|
103
|
-
filtered_d = {d["id"]: d for d in find_objects(objects=list(devices_d.values()), **kwargs)}
|
98
|
+
nbf_devices: DiDAny = getattr(getattr(self.tree, app), model)
|
104
99
|
|
105
|
-
for member_id, device_d in
|
100
|
+
for member_id, device_d in nbf_devices.items():
|
106
101
|
if device_d["virtual_chassis"]:
|
107
|
-
master_id = device_d["virtual_chassis"]["master"]["id"]
|
102
|
+
master_id: int = device_d["virtual_chassis"]["master"]["id"]
|
108
103
|
if member_id != master_id:
|
109
|
-
if master_d :=
|
110
|
-
master_d["_vc_members"][member_id] =
|
104
|
+
if master_d := nbf_devices.get(master_id, {}):
|
105
|
+
master_d["_vc_members"][member_id] = nbf_devices[member_id]
|
111
106
|
|
112
|
-
def _join_dcim_devices(self
|
107
|
+
def _join_dcim_devices(self) -> LInt:
|
113
108
|
"""Create additional key/values to represent devices similar to the WEB UI.
|
114
109
|
|
115
110
|
Create key/values: _interfaces, _front_ports, _console_ports, etc.
|
116
111
|
|
117
|
-
:param kwargs: Filtering parameters.
|
118
|
-
|
119
112
|
:return: IDs of joined ports. Update NbTree.dcim.devices object.
|
120
113
|
"""
|
121
114
|
# models
|
@@ -124,69 +117,71 @@ class Joiner:
|
|
124
117
|
# noinspection PyProtectedMember
|
125
118
|
extra_keys: LStr = BaseC._extra_keys["dcim/devices/"] # pylint: disable=W0212
|
126
119
|
extra_keys = [s for s in extra_keys if s != "_vc_members"]
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
120
|
+
extra_models: LStr = [s.lstrip("_") for s in extra_keys]
|
121
|
+
nbf_devices: DiDAny = getattr(getattr(self.tree, app), model)
|
122
|
+
|
123
|
+
# joined interfaces, need find assigned ip-addresses
|
124
|
+
intf_ids: SInt = set()
|
125
|
+
|
126
|
+
# set _interfaces, _front_ports, etc.
|
127
|
+
for extra_model in extra_models:
|
128
|
+
ports_d: DiDAny = getattr(getattr(self.tree, app), extra_model)
|
129
|
+
for nb_port in ports_d.values():
|
130
|
+
port_name = nb_port["name"]
|
131
|
+
device_id: int = nb_port["device"]["id"]
|
132
|
+
nb_device: DAny = nbf_devices.get(device_id, {})
|
133
|
+
extra_key = f"_{extra_model}"
|
134
|
+
|
135
|
+
# if device has been downloaded from netbox
|
136
|
+
if extra_key in nb_device:
|
137
|
+
nb_device[extra_key][port_name] = nb_port
|
138
|
+
|
139
|
+
# interface ids to assign ip_addresses
|
140
|
+
if extra_model == "interfaces":
|
141
|
+
intf_id = nb_port["id"]
|
142
|
+
intf_ids.add(intf_id)
|
143
|
+
|
144
|
+
# join virtual chassis interfaces to master
|
145
|
+
for device_id, nb_device in nbf_devices.items():
|
146
|
+
for vc_member in nb_device["_vc_members"].values():
|
147
|
+
master_id = vc_member["virtual_chassis"]["master"]["id"]
|
148
|
+
if device_id == master_id:
|
149
|
+
for intf_name, nb_intf in vc_member["_interfaces"].items():
|
150
|
+
if intf_name not in nb_device["_interfaces"]:
|
151
|
+
nb_device["_interfaces"][intf_name] = nb_intf
|
152
|
+
|
153
|
+
return sorted(intf_ids)
|
154
|
+
|
155
|
+
def _join_ip_addresses(self, intf_ids: LInt, app: str) -> None:
|
156
|
+
"""Add NbTree.ipam.ip_address data to NbTree.dcim.interfaces._ip_addresses or VM.
|
161
157
|
|
162
158
|
:param intf_ids: Interface IDs that was joined in device/VM.
|
163
159
|
:param app: Application name: "dcim", "virtualization"
|
164
160
|
|
165
|
-
:return: None. Update NbTree
|
161
|
+
:return: None. Update NbTree.ipam.ip_addresses.
|
166
162
|
"""
|
167
163
|
model = "interfaces"
|
168
164
|
object_type = "virtualization.vminterface" if app == "virtualization" else "dcim.interface"
|
169
165
|
intfs_d: DiDAny = getattr(getattr(self.tree, app), model)
|
170
166
|
params = {"id": intf_ids}
|
171
|
-
intfs_d = {
|
167
|
+
intfs_d = {
|
168
|
+
d["id"]: d for d in nb_parser.find_objects(objects=list(intfs_d.values()), **params)
|
169
|
+
}
|
172
170
|
|
173
171
|
app = "ipam"
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
intf_d: DAny = intfs_d.get(assigned_object_id, {}) # pylint: disable=E1101
|
188
|
-
if _key in intf_d:
|
189
|
-
intf_d[_key][address] = address_d
|
172
|
+
extra_model = "ip_addresses"
|
173
|
+
extra_key = f"_{extra_model}"
|
174
|
+
nbf_addresses: DiDAny = getattr(getattr(self.tree, app), extra_model)
|
175
|
+
|
176
|
+
for nb_addr in nbf_addresses.values():
|
177
|
+
address = nb_addr["address"]
|
178
|
+
if nb_addr["assigned_object_type"] == object_type:
|
179
|
+
if assigned_object_id := nb_addr["assigned_object_id"]:
|
180
|
+
nb_intf: DAny = intfs_d.get(assigned_object_id, {}) # pylint: disable=E1101
|
181
|
+
|
182
|
+
# if device has been downloaded from netbox
|
183
|
+
if extra_key in nb_intf:
|
184
|
+
nb_intf[extra_key][address] = nb_addr
|
190
185
|
|
191
186
|
def join_ipam_ipv4(self) -> None:
|
192
187
|
"""Create additional keys to represent ipam similar to the WEB UI.
|