annet 0.16.34__tar.gz → 0.16.36__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.
Potentially problematic release.
This version of annet might be problematic. Click here for more details.
- {annet-0.16.34/annet.egg-info → annet-0.16.36}/PKG-INFO +1 -1
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/manufacturer.py +1 -1
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/models.py +2 -1
- annet-0.16.36/annet/adapters/netbox/common/query.py +61 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/v37/storage.py +81 -42
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/devdb/data/devdb.json +3 -1
- {annet-0.16.34 → annet-0.16.36}/annet/bgp_models.py +5 -2
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/prefix_lists.py +1 -2
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/arista.order +1 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/huawei.deploy +1 -1
- {annet-0.16.34 → annet-0.16.36/annet.egg-info}/PKG-INFO +1 -1
- annet-0.16.34/annet/adapters/netbox/common/query.py +0 -26
- {annet-0.16.34 → annet-0.16.36}/AUTHORS +0 -0
- {annet-0.16.34 → annet-0.16.36}/LICENSE +0 -0
- {annet-0.16.34 → annet-0.16.36}/MANIFEST.in +0 -0
- {annet-0.16.34 → annet-0.16.36}/README.md +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/file/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/file/provider.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/provider.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annet.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/command.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/diff.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/errors.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/lib.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/output.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/patching.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/annlib/types.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/api/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/argparse.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/cli.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/cli_args.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/configs/context.yml +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/configs/logging.yaml +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/connectors.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/deploy.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/diff.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/executor.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/filtering.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/gen.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/base.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/common/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/common/initial.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/entire.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/exceptions.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/jsonfragment.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/partial.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/perf.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/ref.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/generators/result.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/hardware.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/implicit.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/lib.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/basemodel.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/device_models.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/executor.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/match_args.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/models_converter.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/peer_models.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/port_processor.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/mesh/registry.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/output.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/parallel.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/patching.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/reference.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/action.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/condition.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/match_builder.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/policy.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/result.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/routemap.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl/statement_builder.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/aspath.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/community.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/cumulus_frr.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/entities.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/execute.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/policy.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rpl_generators/rd.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/b4com/iface.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/common.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/deploying.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/patching.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/routeros/file.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/storage.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/tabparser.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/text_term_format.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/tracing.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet/types.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet.egg-info/requires.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/example/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/mesh_example/bgp.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/mesh_example/mesh_logic.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/rpl_example/__init__.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/rpl_example/generator.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/rpl_example/items.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/rpl_example/mesh.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/annet_generators/rpl_example/route_policy.py +0 -0
- {annet-0.16.34 → annet-0.16.36}/requirements.txt +0 -0
- {annet-0.16.34 → annet-0.16.36}/setup.cfg +0 -0
- {annet-0.16.34 → annet-0.16.36}/setup.py +0 -0
|
@@ -54,7 +54,7 @@ def get_breed(manufacturer: str, model: str):
|
|
|
54
54
|
return "bcom-os"
|
|
55
55
|
elif manufacturer == "MikroTik":
|
|
56
56
|
return "routeros"
|
|
57
|
-
elif manufacturer
|
|
57
|
+
elif manufacturer in ("Moxa", "Nebius"):
|
|
58
58
|
return "moxa"
|
|
59
59
|
elif manufacturer == "PC":
|
|
60
60
|
return "pc"
|
|
@@ -214,7 +214,8 @@ class NetboxDevice(Entity):
|
|
|
214
214
|
return type(self) is type(other) and self.url == other.url
|
|
215
215
|
|
|
216
216
|
def is_pc(self) -> bool:
|
|
217
|
-
|
|
217
|
+
custom_breed_pc = ("Mellanox", "NVIDIA", "Moxa", "Nebius")
|
|
218
|
+
return self.device_type.manufacturer.name in custom_breed_pc or self.breed == "pc"
|
|
218
219
|
|
|
219
220
|
def _make_interface(self, name: str, type: InterfaceType) -> Interface:
|
|
220
221
|
return Interface(
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import cast, List, Union, Iterable, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
from annet.storage import Query
|
|
6
|
+
|
|
7
|
+
FIELD_VALUE_SEPARATOR = ":"
|
|
8
|
+
ALLOWED_GLOB_GROUPS = ["site", "tag", "role"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Filter(TypedDict, total=False):
|
|
12
|
+
site: list[str]
|
|
13
|
+
tag: list[str]
|
|
14
|
+
role: list[str]
|
|
15
|
+
name: list[str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class NetboxQuery(Query):
|
|
20
|
+
query: List[str]
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def new(
|
|
24
|
+
cls, query: Union[str, Iterable[str]],
|
|
25
|
+
hosts_range: Optional[slice] = None,
|
|
26
|
+
) -> "NetboxQuery":
|
|
27
|
+
if hosts_range is not None:
|
|
28
|
+
raise ValueError("host_range is not supported")
|
|
29
|
+
return cls(query=list(query))
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def globs(self):
|
|
33
|
+
# We process every query host as a glob
|
|
34
|
+
return self.query
|
|
35
|
+
|
|
36
|
+
def parse_query(self) -> Filter:
|
|
37
|
+
query_groups = defaultdict(list)
|
|
38
|
+
for q in self.globs:
|
|
39
|
+
if FIELD_VALUE_SEPARATOR in q:
|
|
40
|
+
glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2)
|
|
41
|
+
if glob_type not in ALLOWED_GLOB_GROUPS:
|
|
42
|
+
raise Exception(f"unknown query type: '{glob_type}'")
|
|
43
|
+
if not param:
|
|
44
|
+
raise Exception(f"empty param for '{glob_type}'")
|
|
45
|
+
query_groups[glob_type].append(param)
|
|
46
|
+
else:
|
|
47
|
+
query_groups["name"].append(q)
|
|
48
|
+
|
|
49
|
+
query_groups.default_factory = None
|
|
50
|
+
return cast(Filter, query_groups)
|
|
51
|
+
|
|
52
|
+
def is_empty(self) -> bool:
|
|
53
|
+
return len(self.query) == 0
|
|
54
|
+
|
|
55
|
+
def is_host_query(self) -> bool:
|
|
56
|
+
if not self.globs:
|
|
57
|
+
return False
|
|
58
|
+
for q in self.globs:
|
|
59
|
+
if FIELD_VALUE_SEPARATOR in q:
|
|
60
|
+
return False
|
|
61
|
+
return True
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from logging import getLogger
|
|
2
|
-
from typing import Any, Optional, List, Union, Dict
|
|
3
|
-
from ipaddress import ip_interface
|
|
4
|
-
from collections import defaultdict
|
|
5
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
6
|
|
|
7
7
|
from adaptix import P
|
|
8
8
|
from adaptix.conversion import impl_converter, link, link_constant
|
|
@@ -13,7 +13,7 @@ from annet.adapters.netbox.common import models
|
|
|
13
13
|
from annet.adapters.netbox.common.manufacturer import (
|
|
14
14
|
get_hw, get_breed,
|
|
15
15
|
)
|
|
16
|
-
from annet.adapters.netbox.common.query import NetboxQuery
|
|
16
|
+
from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATOR
|
|
17
17
|
from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
|
|
18
18
|
from annet.annlib.netdev.views.hardware import HardwareView
|
|
19
19
|
from annet.storage import Storage, Device, Interface
|
|
@@ -101,6 +101,9 @@ class NetboxStorageV37(Storage):
|
|
|
101
101
|
self.exact_host_filter = opts.exact_host_filter
|
|
102
102
|
self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx)
|
|
103
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] = {}
|
|
104
107
|
|
|
105
108
|
def __enter__(self):
|
|
106
109
|
return self
|
|
@@ -136,6 +139,37 @@ class NetboxStorageV37(Storage):
|
|
|
136
139
|
) -> List[models.NetboxDevice]:
|
|
137
140
|
if isinstance(query, list):
|
|
138
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
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _make_devices(
|
|
166
|
+
self,
|
|
167
|
+
query: NetboxQuery,
|
|
168
|
+
preload_neighbors=False,
|
|
169
|
+
use_mesh=None,
|
|
170
|
+
preload_extra_fields=False,
|
|
171
|
+
**kwargs,
|
|
172
|
+
) -> List[models.NetboxDevice]:
|
|
139
173
|
device_ids = {
|
|
140
174
|
device.id: extend_device(
|
|
141
175
|
device=device,
|
|
@@ -148,6 +182,9 @@ class NetboxStorageV37(Storage):
|
|
|
148
182
|
if not device_ids:
|
|
149
183
|
return []
|
|
150
184
|
|
|
185
|
+
for device in device_ids.values():
|
|
186
|
+
self._record_device(device)
|
|
187
|
+
|
|
151
188
|
interfaces = self._load_interfaces(list(device_ids))
|
|
152
189
|
neighbours = {x.id: x for x in self._load_neighbours(interfaces)}
|
|
153
190
|
neighbours_seen: dict[str, set] = defaultdict(set)
|
|
@@ -162,32 +199,22 @@ class NetboxStorageV37(Storage):
|
|
|
162
199
|
|
|
163
200
|
return list(device_ids.values())
|
|
164
201
|
|
|
202
|
+
def _record_device(self, device: models.NetboxDevice):
|
|
203
|
+
self._id_devices[device.id] = device
|
|
204
|
+
self._short_name_devices[device.name] = device
|
|
205
|
+
if not self.exact_host_filter:
|
|
206
|
+
short_name = device.name.split(".")[0]
|
|
207
|
+
self._short_name_devices[short_name] = device
|
|
208
|
+
|
|
165
209
|
def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
|
|
166
210
|
if not query.globs:
|
|
167
211
|
return []
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for grp, params in query_groups.items():
|
|
175
|
-
if not params:
|
|
176
|
-
continue
|
|
177
|
-
try:
|
|
178
|
-
new_devices = self.netbox.dcim_all_devices(**{grp: params}).results
|
|
179
|
-
except Exception as e:
|
|
180
|
-
# tag and site lookup returns 400 in case of unknown tag or site
|
|
181
|
-
if "is not one of the available choices" in str(e):
|
|
182
|
-
continue
|
|
183
|
-
raise
|
|
184
|
-
if grp == "name__ic":
|
|
185
|
-
new_devices = [device for device in new_devices if _match_query(query, device)]
|
|
186
|
-
for device in new_devices:
|
|
187
|
-
if device.id not in device_ids:
|
|
188
|
-
device_ids.add(device.id)
|
|
189
|
-
devices.extend(new_devices)
|
|
190
|
-
return devices
|
|
212
|
+
query_groups = parse_glob(self.exact_host_filter, query)
|
|
213
|
+
return [
|
|
214
|
+
device
|
|
215
|
+
for device in self.netbox.dcim_all_devices(**query_groups).results
|
|
216
|
+
if _match_query(self.exact_host_filter, query, device)
|
|
217
|
+
]
|
|
191
218
|
|
|
192
219
|
def _extend_interfaces(self, interfaces: List[models.Interface]) -> List[models.Interface]:
|
|
193
220
|
extended_ifaces = {
|
|
@@ -238,6 +265,9 @@ class NetboxStorageV37(Storage):
|
|
|
238
265
|
self, obj_id, preload_neighbors=False, use_mesh=None,
|
|
239
266
|
**kwargs,
|
|
240
267
|
) -> models.NetboxDevice:
|
|
268
|
+
if obj_id in self._id_devices:
|
|
269
|
+
return self._id_devices[obj_id]
|
|
270
|
+
|
|
241
271
|
device = self.netbox.dcim_device(obj_id)
|
|
242
272
|
interfaces = self._load_interfaces([device.id])
|
|
243
273
|
neighbours = self._load_neighbours(interfaces)
|
|
@@ -248,6 +278,7 @@ class NetboxStorageV37(Storage):
|
|
|
248
278
|
interfaces=interfaces,
|
|
249
279
|
neighbours=neighbours,
|
|
250
280
|
)
|
|
281
|
+
self._record_device(res)
|
|
251
282
|
return res
|
|
252
283
|
|
|
253
284
|
def flush_perf(self):
|
|
@@ -272,9 +303,20 @@ class NetboxStorageV37(Storage):
|
|
|
272
303
|
return res
|
|
273
304
|
|
|
274
305
|
|
|
275
|
-
def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
276
|
-
|
|
277
|
-
|
|
306
|
+
def _match_query(exact_host_filter: bool, query: NetboxQuery, device_data: api_models.Device) -> bool:
|
|
307
|
+
"""
|
|
308
|
+
Additional filtering after netbox due to limited backend logic.
|
|
309
|
+
"""
|
|
310
|
+
if exact_host_filter:
|
|
311
|
+
return True # nothing to check, all filtering is done by netbox
|
|
312
|
+
hostnames = [subquery.strip() for subquery in query.globs if FIELD_VALUE_SEPARATOR not in subquery]
|
|
313
|
+
if not hostnames:
|
|
314
|
+
return True # no hostnames to check
|
|
315
|
+
|
|
316
|
+
short_name = device_data.name.split(".")[0]
|
|
317
|
+
for hostname in hostnames:
|
|
318
|
+
hostname = hostname.strip().rstrip(".")
|
|
319
|
+
if short_name == hostname or device_data.name == hostname:
|
|
278
320
|
return True
|
|
279
321
|
return False
|
|
280
322
|
|
|
@@ -294,20 +336,17 @@ def _hostname_dot_hack(raw_query: str) -> str:
|
|
|
294
336
|
if isinstance(raw_query, list):
|
|
295
337
|
for i, name in enumerate(raw_query):
|
|
296
338
|
raw_query[i] = add_dot(name)
|
|
339
|
+
elif isinstance(raw_query, str):
|
|
340
|
+
raw_query = add_dot(raw_query)
|
|
297
341
|
|
|
298
342
|
return raw_query
|
|
299
343
|
|
|
300
344
|
|
|
301
|
-
def parse_glob(
|
|
302
|
-
query_groups
|
|
303
|
-
|
|
304
|
-
if
|
|
305
|
-
|
|
306
|
-
if glob_type not in query_groups:
|
|
307
|
-
raise Exception(f"unknown query type: '{glob_type}'")
|
|
308
|
-
if not param:
|
|
309
|
-
raise Exception(f"empty param for '{glob_type}'")
|
|
310
|
-
query_groups[glob_type].append(param)
|
|
345
|
+
def parse_glob(exact_host_filter: bool, query: NetboxQuery) -> dict[str, list[str]]:
|
|
346
|
+
query_groups = cast(dict[str, list[str]], query.parse_query())
|
|
347
|
+
if names := query_groups.pop("name", None):
|
|
348
|
+
if exact_host_filter:
|
|
349
|
+
query_groups["name__ie"] = names
|
|
311
350
|
else:
|
|
312
|
-
query_groups["name__ic"]
|
|
351
|
+
query_groups["name__ic"] = [_hostname_dot_hack(name) for name in names]
|
|
313
352
|
return query_groups
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"Nokia.NS7750": " 7750",
|
|
105
105
|
"Nokia.SR_1s": "SR-1s",
|
|
106
106
|
|
|
107
|
-
"PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa|NVIDIA|[Aa]sterfusion CX|[Uu]fi[Ss]pace)",
|
|
107
|
+
"PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa|NVIDIA|Nebius|[Aa]sterfusion CX|[Uu]fi[Ss]pace)",
|
|
108
108
|
|
|
109
109
|
"PC.Whitebox": "([Mm]ellanox SN|[Ee]dge-?[Cc]ore|NVIDIA|[Aa]sterfusion CX|[Uu]fi[Ss]pace)",
|
|
110
110
|
"PC.Whitebox.Mellanox": "[Mm]ellanox",
|
|
@@ -132,6 +132,8 @@
|
|
|
132
132
|
"PC.Whitebox.Ufispace.S": " S",
|
|
133
133
|
"PC.Whitebox.Ufispace.S.S9100": " S91\\d\\d",
|
|
134
134
|
"PC.Whitebox.Ufispace.S.S9100.S9110_32X": " S9110-32X",
|
|
135
|
+
"PC.Nebius": "^Nebius",
|
|
136
|
+
"PC.Nebius.NB-E-BR-DCU-AST2600": "^Nebius NB-E-BR-DCU-AST2600",
|
|
135
137
|
|
|
136
138
|
"RouterOS": "^([Rr]outer[Oo][Ss]|[Mm]ikro[Tt]ik)",
|
|
137
139
|
|
|
@@ -245,8 +245,10 @@ class VrfOptions:
|
|
|
245
245
|
l2vpn_evpn: FamilyOptions
|
|
246
246
|
|
|
247
247
|
vrf_name_global: Optional[str] = None
|
|
248
|
-
|
|
248
|
+
import_policy: str = ""
|
|
249
|
+
export_policy: str = ""
|
|
249
250
|
as_path_relax: bool = False
|
|
251
|
+
l3vni: Optional[int] = None
|
|
250
252
|
rt_import: list[str] = field(default_factory=list)
|
|
251
253
|
rt_export: list[str] = field(default_factory=list)
|
|
252
254
|
rt_import_v4: list[str] = field(default_factory=list)
|
|
@@ -281,7 +283,7 @@ class BgpConfig:
|
|
|
281
283
|
peers: list[Peer]
|
|
282
284
|
|
|
283
285
|
|
|
284
|
-
def _used_policies(peer: Union[Peer, PeerGroup]) -> Iterable[str]:
|
|
286
|
+
def _used_policies(peer: Union[Peer, PeerGroup, VrfOptions]) -> Iterable[str]:
|
|
285
287
|
if peer.import_policy:
|
|
286
288
|
yield peer.import_policy
|
|
287
289
|
if peer.export_policy:
|
|
@@ -309,6 +311,7 @@ def extract_policies(config: BgpConfig) -> Sequence[str]:
|
|
|
309
311
|
for group in vrf.groups:
|
|
310
312
|
result.extend(_used_policies(group))
|
|
311
313
|
result.extend(_used_redistribute_policies(vrf))
|
|
314
|
+
result.extend(_used_policies(vrf))
|
|
312
315
|
for group in config.global_options.groups:
|
|
313
316
|
result.extend(_used_policies(group))
|
|
314
317
|
for peer in config.peers:
|
|
@@ -128,8 +128,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
128
128
|
name,
|
|
129
129
|
f"seq {i * 5 + 5}",
|
|
130
130
|
"permit",
|
|
131
|
-
|
|
132
|
-
str(addr_mask.network.prefixlen),
|
|
131
|
+
addr_mask.with_prefixlen,
|
|
133
132
|
) + (
|
|
134
133
|
("ge", str(match.greater_equal)) if match.greater_equal is not None else ()
|
|
135
134
|
) + (
|
|
@@ -32,7 +32,7 @@ undo route-distinguisher *
|
|
|
32
32
|
dialog: Warning: All VPN targets of this EVPN instance and all EVPN address family-related configurations under BGP will be deleted. Continue? [Y/N]: ::: Y
|
|
33
33
|
|
|
34
34
|
save
|
|
35
|
-
dialog: Warning: The current configuration will be written to the device. Continue
|
|
35
|
+
dialog: /Warning: The current configuration will be written to the device. Continue\? \[Y/N\]:?/ ::: Y
|
|
36
36
|
dialog: Warning: The current configuration will be written to the device. Are you sure to continue? [Y/N]: ::: Y
|
|
37
37
|
dialog: Are you sure to continue?[Y/N] ::: Y
|
|
38
38
|
dialog: Are you sure to continue? [Y/N] ::: Y
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import List, Union, Iterable, Optional
|
|
3
|
-
|
|
4
|
-
from annet.storage import Query
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class NetboxQuery(Query):
|
|
9
|
-
query: List[str]
|
|
10
|
-
|
|
11
|
-
@classmethod
|
|
12
|
-
def new(
|
|
13
|
-
cls, query: Union[str, Iterable[str]],
|
|
14
|
-
hosts_range: Optional[slice] = None,
|
|
15
|
-
) -> "NetboxQuery":
|
|
16
|
-
if hosts_range is not None:
|
|
17
|
-
raise ValueError("host_range is not supported")
|
|
18
|
-
return cls(query=list(query))
|
|
19
|
-
|
|
20
|
-
@property
|
|
21
|
-
def globs(self):
|
|
22
|
-
# We process every query host as a glob
|
|
23
|
-
return self.query
|
|
24
|
-
|
|
25
|
-
def is_empty(self) -> bool:
|
|
26
|
-
return len(self.query) == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|