annet 2.3.1__tar.gz → 2.5.0__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-2.3.1/annet.egg-info → annet-2.5.0}/PKG-INFO +4 -3
- annet-2.5.0/annet/adapters/netbox/common/adapter.py +60 -0
- annet-2.5.0/annet/adapters/netbox/common/models.py +287 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/status_client.py +7 -0
- annet-2.3.1/annet/adapters/netbox/v37/storage.py → annet-2.5.0/annet/adapters/netbox/common/storage_base.py +80 -162
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/provider.py +20 -7
- {annet-2.3.1/annet/adapters/netbox/common → annet-2.5.0/annet/adapters/netbox/v24}/models.py +1 -1
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/v24/storage.py +8 -2
- annet-2.5.0/annet/adapters/netbox/v37/models.py +60 -0
- annet-2.5.0/annet/adapters/netbox/v37/storage.py +91 -0
- annet-2.5.0/annet/adapters/netbox/v41/models.py +78 -0
- annet-2.5.0/annet/adapters/netbox/v41/storage.py +91 -0
- annet-2.5.0/annet/adapters/netbox/v42/models.py +63 -0
- annet-2.5.0/annet/adapters/netbox/v42/storage.py +91 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/netdev/devdb/data/devdb.json +2 -0
- annet-2.5.0/annet/rulebook/texts/pc.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0/annet.egg-info}/PKG-INFO +4 -3
- {annet-2.3.1 → annet-2.5.0}/annet.egg-info/SOURCES.txt +10 -0
- {annet-2.3.1 → annet-2.5.0}/annet.egg-info/requires.txt +1 -1
- annet-2.5.0/annet_generators/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/setup.py +1 -1
- {annet-2.3.1 → annet-2.5.0}/AUTHORS +0 -0
- {annet-2.3.1 → annet-2.5.0}/LICENSE +0 -0
- {annet-2.3.1 → annet-2.5.0}/MANIFEST.in +0 -0
- {annet-2.3.1 → annet-2.5.0}/README.md +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/file/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/file/provider.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/client.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/query.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-2.3.1/annet/annlib/netdev → annet-2.5.0/annet/adapters/netbox/v41}/__init__.py +0 -0
- {annet-2.3.1/annet/annlib/netdev/views → annet-2.5.0/annet/adapters/netbox/v42}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annet.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/command.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/diff.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/errors.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/filter_acl.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/jsontools.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/lib.py +0 -0
- {annet-2.3.1/annet/annlib/rbparser → annet-2.5.0/annet/annlib/netdev}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/netdev/db.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-2.3.1/annet/annlib/rulebook → annet-2.5.0/annet/annlib/netdev/views}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/output.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/patching.py +0 -0
- {annet-2.3.1/annet/generators/common → annet-2.5.0/annet/annlib/rbparser}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rbparser/acl.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rbparser/platform.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-2.3.1/annet/rulebook/arista → annet-2.5.0/annet/annlib/rulebook}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/rulebook/common.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/tabparser.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/annlib/types.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/api/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/argparse.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/bgp_models.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/cli.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/cli_args.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/configs/context.yml +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/configs/logging.yaml +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/connectors.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/deploy.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/deploy_ui.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/diff.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/executor.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/filtering.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/gen.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/base.py +0 -0
- {annet-2.3.1/annet/rulebook/b4com → annet-2.5.0/annet/generators/common}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/common/initial.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/entire.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/exceptions.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/jsonfragment.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/partial.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/perf.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/ref.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/generators/result.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/hardware.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/implicit.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/lib.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/basemodel.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/device_models.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/executor.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/match_args.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/models_converter.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/peer_models.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/port_processor.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/mesh/registry.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/output.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/parallel.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/patching.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/reference.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/action.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/condition.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/match_builder.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/policy.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/result.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/routemap.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl/statement_builder.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/aspath.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/community.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/cumulus_frr.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/entities.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/execute.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/policy.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/prefix_lists.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rpl_generators/rd.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/__init__.py +0 -0
- {annet-2.3.1/annet/rulebook/cisco → annet-2.5.0/annet/rulebook/arista}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/arista/aaa.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/arista/iface.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/aruba/misc.py +0 -0
- {annet-2.3.1/annet/rulebook/huawei → annet-2.5.0/annet/rulebook/b4com}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/b4com/file.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/b4com/iface.py +0 -0
- {annet-2.3.1/annet/rulebook/nexus → annet-2.5.0/annet/rulebook/cisco}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/cisco/iface.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/cisco/misc.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/common.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/deploying.py +0 -0
- {annet-2.3.1/annet/rulebook/routeros → annet-2.5.0/annet/rulebook/huawei}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/huawei/iface.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/huawei/misc.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-2.3.1/annet_generators → annet-2.5.0/annet/rulebook/nexus}/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/nexus/iface.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/patching.py +0 -0
- /annet-2.3.1/annet/rulebook/texts/pc.deploy → /annet-2.5.0/annet/rulebook/routeros/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/routeros/file.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/arista.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/arista.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/aruba.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/b4com.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/cisco.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/huawei.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/juniper.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/nexus.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/pc.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/pc.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/routeros.order +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/storage.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/tabparser.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/text_term_format.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/tracing.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet/types.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet.egg-info/dependency_links.txt +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet.egg-info/entry_points.txt +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet.egg-info/top_level.txt +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/example/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/example/lldp.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/mesh_example/bgp.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/mesh_example/mesh_logic.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/rpl_example/__init__.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/rpl_example/generator.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/rpl_example/items.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/rpl_example/mesh.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/annet_generators/rpl_example/route_policy.py +0 -0
- {annet-2.3.1 → annet-2.5.0}/requirements.txt +0 -0
- {annet-2.3.1 → annet-2.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: annet
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: annet
|
|
5
5
|
Home-page: https://github.com/annetutil/annet
|
|
6
6
|
License: MIT
|
|
@@ -21,9 +21,10 @@ 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.2.
|
|
24
|
+
Requires-Dist: annetbox[sync]>=0.2.2; extra == "netbox"
|
|
25
25
|
Dynamic: home-page
|
|
26
26
|
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
27
28
|
Dynamic: provides-extra
|
|
28
29
|
Dynamic: requires-dist
|
|
29
30
|
Dynamic: requires-python
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from abc import abstractmethod, ABC
|
|
2
|
+
from typing import Protocol, Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from annet.annlib.netdev.views.hardware import HardwareView
|
|
5
|
+
from .manufacturer import get_breed, get_hw
|
|
6
|
+
from .models import NetboxDevice, Interface, IpAddress, Prefix
|
|
7
|
+
|
|
8
|
+
NetboxDeviceT = TypeVar("NetboxDeviceT", bound=NetboxDevice)
|
|
9
|
+
InterfaceT = TypeVar("InterfaceT", bound=Interface)
|
|
10
|
+
IpAddressT = TypeVar("IpAddressT", bound=IpAddress)
|
|
11
|
+
PrefixT = TypeVar("PrefixT", bound=Prefix)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_device_breed(device: NetboxDeviceT) -> str:
|
|
15
|
+
if device.device_type and device.device_type.manufacturer:
|
|
16
|
+
return get_breed(
|
|
17
|
+
device.device_type.manufacturer.name,
|
|
18
|
+
device.device_type.model,
|
|
19
|
+
)
|
|
20
|
+
return ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_device_hw(device: NetboxDeviceT) -> HardwareView:
|
|
24
|
+
if device.device_type and device.device_type.manufacturer:
|
|
25
|
+
return get_hw(
|
|
26
|
+
device.device_type.manufacturer.name,
|
|
27
|
+
device.device_type.model,
|
|
28
|
+
device.platform.name if device.platform else "",
|
|
29
|
+
)
|
|
30
|
+
return HardwareView("", "")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NetboxAdapter(ABC, Generic[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT]):
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def list_all_fqdns(self) -> list[str]:
|
|
36
|
+
raise NotImplementedError()
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def list_devices(self, query: dict[str, list[str]]) -> list[NetboxDeviceT]:
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def get_device(self, device_id: int) -> NetboxDeviceT:
|
|
44
|
+
raise NotImplementedError()
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def list_interfaces_by_devices(self, device_ids: list[int]) -> list[InterfaceT]:
|
|
48
|
+
raise NotImplementedError()
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def list_interfaces(self, ids: list[int]) -> list[InterfaceT]:
|
|
52
|
+
raise NotImplementedError()
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def list_ipaddr_by_ifaces(self, iface_ids: list[int]) -> list[IpAddressT]:
|
|
56
|
+
raise NotImplementedError()
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def list_ipprefixes(self, prefixes: list[str]) -> list[PrefixT]:
|
|
60
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from ipaddress import ip_interface, IPv6Interface
|
|
5
|
+
from typing import List, Optional, Any, Dict, Sequence, TypeVar, Generic
|
|
6
|
+
|
|
7
|
+
from annet.annlib.netdev.views.dump import DumpableView
|
|
8
|
+
from annet.annlib.netdev.views.hardware import HardwareView, lag_name, svi_name
|
|
9
|
+
from annet.storage import Storage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Entity(DumpableView):
|
|
14
|
+
id: int
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def _dump__list_key(self):
|
|
19
|
+
return self.name
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Label:
|
|
24
|
+
value: str
|
|
25
|
+
label: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class IpFamily:
|
|
30
|
+
value: int
|
|
31
|
+
label: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class DeviceType:
|
|
36
|
+
id: int
|
|
37
|
+
manufacturer: Entity
|
|
38
|
+
model: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class DeviceIp(DumpableView):
|
|
43
|
+
id: int
|
|
44
|
+
display: str
|
|
45
|
+
address: str
|
|
46
|
+
family: int
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def _dump__list_key(self):
|
|
50
|
+
return self.address
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Prefix(DumpableView):
|
|
55
|
+
id: int
|
|
56
|
+
prefix: str
|
|
57
|
+
# `site` deprecated since v4.2, replace in derived classes.
|
|
58
|
+
vrf: Optional[Entity]
|
|
59
|
+
tenant: Optional[Entity]
|
|
60
|
+
vlan: Optional[Entity]
|
|
61
|
+
role: Optional[Entity]
|
|
62
|
+
status: Label
|
|
63
|
+
is_pool: bool
|
|
64
|
+
custom_fields: dict[str, Any]
|
|
65
|
+
created: datetime
|
|
66
|
+
last_updated: datetime
|
|
67
|
+
description: Optional[str] = ""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def _dump__list_key(self):
|
|
71
|
+
return self.prefix
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_PrefixT = TypeVar("_PrefixT", bound=Prefix)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class IpAddress(DumpableView, Generic[_PrefixT]):
|
|
79
|
+
id: int
|
|
80
|
+
assigned_object_id: int | None
|
|
81
|
+
display: str
|
|
82
|
+
family: IpFamily
|
|
83
|
+
address: str
|
|
84
|
+
status: Label
|
|
85
|
+
tags: List[Entity]
|
|
86
|
+
created: datetime
|
|
87
|
+
last_updated: datetime
|
|
88
|
+
prefix: Optional[_PrefixT] = None
|
|
89
|
+
vrf: Optional[Entity] = None
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def _dump__list_key(self):
|
|
93
|
+
return self.address
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class InterfaceConnectedEndpoint(Entity):
|
|
98
|
+
device: Entity
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class InterfaceType:
|
|
103
|
+
value: str
|
|
104
|
+
label: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class InterfaceMode:
|
|
109
|
+
value: str
|
|
110
|
+
label: str
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class InterfaceVlan(Entity):
|
|
115
|
+
vid: int
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def vrf_object(vrf: str | None) -> Entity | None:
|
|
119
|
+
if vrf is None:
|
|
120
|
+
return None
|
|
121
|
+
else:
|
|
122
|
+
return Entity(id=0, name=vrf)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_IpAddressT = TypeVar("_IpAddressT", bound=IpAddress)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class Interface(Entity, Generic[_IpAddressT]):
|
|
130
|
+
device: Entity
|
|
131
|
+
enabled: bool
|
|
132
|
+
description: str
|
|
133
|
+
type: InterfaceType
|
|
134
|
+
connected_endpoints: Optional[list[InterfaceConnectedEndpoint]]
|
|
135
|
+
mode: Optional[InterfaceMode]
|
|
136
|
+
untagged_vlan: Optional[InterfaceVlan]
|
|
137
|
+
tagged_vlans: Optional[List[InterfaceVlan]]
|
|
138
|
+
display: str = ""
|
|
139
|
+
ip_addresses: List[_IpAddressT] = field(default_factory=list)
|
|
140
|
+
vrf: Optional[Entity] = None
|
|
141
|
+
mtu: int | None = None
|
|
142
|
+
lag: Entity | None = None
|
|
143
|
+
lag_min_links: int | None = None
|
|
144
|
+
|
|
145
|
+
def add_addr(self, address_mask: str, vrf: str | None) -> None:
|
|
146
|
+
for existing_addr in self.ip_addresses:
|
|
147
|
+
if existing_addr.address == address_mask and (
|
|
148
|
+
(existing_addr.vrf is None and vrf is None) or
|
|
149
|
+
(existing_addr.vrf is not None and existing_addr.vrf.name == vrf)
|
|
150
|
+
):
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
addr = ip_interface(address_mask)
|
|
154
|
+
vrf_obj = vrf_object(vrf)
|
|
155
|
+
if isinstance(addr, IPv6Interface):
|
|
156
|
+
family = IpFamily(value=6, label="IPv6")
|
|
157
|
+
else:
|
|
158
|
+
family = IpFamily(value=4, label="IPv4")
|
|
159
|
+
self._add_new_addr(address_mask, vrf_obj, family)
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def _add_new_addr(self, address_mask: str, vrf: Entity | None, family: IpFamily):
|
|
163
|
+
raise NotImplementedError
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
_InterfaceT = TypeVar("_InterfaceT", bound=Interface)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class NetboxDevice(Entity, Generic[_InterfaceT]):
|
|
171
|
+
url: str
|
|
172
|
+
storage: Storage
|
|
173
|
+
|
|
174
|
+
display: str
|
|
175
|
+
device_type: DeviceType
|
|
176
|
+
# `device_role` deprecated since v4.0, replace in derived classes.
|
|
177
|
+
tenant: Optional[Entity]
|
|
178
|
+
platform: Optional[Entity]
|
|
179
|
+
serial: str
|
|
180
|
+
asset_tag: Optional[str]
|
|
181
|
+
site: Entity
|
|
182
|
+
rack: Optional[Entity]
|
|
183
|
+
position: Optional[float]
|
|
184
|
+
face: Optional[Label]
|
|
185
|
+
status: Label
|
|
186
|
+
primary_ip: Optional[DeviceIp]
|
|
187
|
+
primary_ip4: Optional[DeviceIp]
|
|
188
|
+
primary_ip6: Optional[DeviceIp]
|
|
189
|
+
tags: List[Entity]
|
|
190
|
+
custom_fields: Dict[str, Any]
|
|
191
|
+
created: datetime
|
|
192
|
+
last_updated: datetime
|
|
193
|
+
|
|
194
|
+
fqdn: str
|
|
195
|
+
hostname: str
|
|
196
|
+
hw: Optional[HardwareView]
|
|
197
|
+
breed: str
|
|
198
|
+
|
|
199
|
+
interfaces: List[_InterfaceT]
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def neighbors(self) -> List["Entity"]:
|
|
203
|
+
return [
|
|
204
|
+
endpoint.device
|
|
205
|
+
for iface in self.interfaces
|
|
206
|
+
if iface.connected_endpoints
|
|
207
|
+
for endpoint in iface.connected_endpoints
|
|
208
|
+
if endpoint.device
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# compat
|
|
212
|
+
@property
|
|
213
|
+
def neighbours_fqdns(self) -> list[str]:
|
|
214
|
+
return [dev.name for dev in self.neighbors]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def neighbours_ids(self):
|
|
218
|
+
return [dev.id for dev in self.neighbors]
|
|
219
|
+
|
|
220
|
+
def __hash__(self):
|
|
221
|
+
return hash((self.id, type(self)))
|
|
222
|
+
|
|
223
|
+
def __eq__(self, other):
|
|
224
|
+
return type(self) is type(other) and self.url == other.url
|
|
225
|
+
|
|
226
|
+
def is_pc(self) -> bool:
|
|
227
|
+
custom_breed_pc = ("Mellanox", "NVIDIA", "Moxa", "Nebius")
|
|
228
|
+
return self.device_type.manufacturer.name in custom_breed_pc or self.breed == "pc"
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def _make_interface(self, name: str, type: InterfaceType) -> _InterfaceT:
|
|
232
|
+
raise NotImplementedError
|
|
233
|
+
|
|
234
|
+
def _lag_name(self, lag: int) -> str:
|
|
235
|
+
return lag_name(self.hw, lag)
|
|
236
|
+
|
|
237
|
+
def make_lag(self, lag: int, ports: Sequence[str], lag_min_links: int | None) -> _InterfaceT:
|
|
238
|
+
new_name = self._lag_name(lag)
|
|
239
|
+
for target_interface in self.interfaces:
|
|
240
|
+
if target_interface.name == new_name:
|
|
241
|
+
return target_interface
|
|
242
|
+
lag_interface = self._make_interface(
|
|
243
|
+
name=new_name,
|
|
244
|
+
type=InterfaceType(value="lag", label="Link Aggregation Group (LAG)"),
|
|
245
|
+
)
|
|
246
|
+
lag_interface.lag_min_links = lag_min_links
|
|
247
|
+
for interface in self.interfaces:
|
|
248
|
+
if interface.name in ports:
|
|
249
|
+
interface.lag = lag_interface
|
|
250
|
+
self.interfaces.append(lag_interface)
|
|
251
|
+
return lag_interface
|
|
252
|
+
|
|
253
|
+
def _svi_name(self, svi: int) -> str:
|
|
254
|
+
return svi_name(self.hw, svi)
|
|
255
|
+
|
|
256
|
+
def add_svi(self, svi: int) -> _InterfaceT:
|
|
257
|
+
name = self._svi_name(svi)
|
|
258
|
+
for interface in self.interfaces:
|
|
259
|
+
if interface.name == name:
|
|
260
|
+
return interface
|
|
261
|
+
interface = self._make_interface(
|
|
262
|
+
name=name,
|
|
263
|
+
type=InterfaceType("virtual", "Virtual")
|
|
264
|
+
)
|
|
265
|
+
self.interfaces.append(interface)
|
|
266
|
+
return interface
|
|
267
|
+
|
|
268
|
+
def _subif_name(self, interface: str, subif: int) -> str:
|
|
269
|
+
return f"{interface}.{subif}"
|
|
270
|
+
|
|
271
|
+
def add_subif(self, interface: str, subif: int) -> _InterfaceT:
|
|
272
|
+
name = self._subif_name(interface, subif)
|
|
273
|
+
for target_port in self.interfaces:
|
|
274
|
+
if target_port.name == name:
|
|
275
|
+
return target_port
|
|
276
|
+
target_port = self._make_interface(
|
|
277
|
+
name=name,
|
|
278
|
+
type=InterfaceType("virtual", "Virtual")
|
|
279
|
+
)
|
|
280
|
+
self.interfaces.append(target_port)
|
|
281
|
+
return target_port
|
|
282
|
+
|
|
283
|
+
def find_interface(self, name: str) -> Optional[_InterfaceT]:
|
|
284
|
+
for iface in self.interfaces:
|
|
285
|
+
if iface.name == name:
|
|
286
|
+
return iface
|
|
287
|
+
return None
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Dict
|
|
3
|
+
import re
|
|
3
4
|
|
|
4
5
|
from adaptix import Retort, name_mapping, NameStyle
|
|
5
6
|
from dataclass_rest import get
|
|
@@ -13,6 +14,12 @@ class Status:
|
|
|
13
14
|
netbox_version: str
|
|
14
15
|
plugins: Dict[str, str]
|
|
15
16
|
|
|
17
|
+
@property
|
|
18
|
+
def minor_version(self) -> str:
|
|
19
|
+
if match := re.match(r"\d+\.\d+", self.netbox_version):
|
|
20
|
+
return match.group(0)
|
|
21
|
+
return ""
|
|
22
|
+
|
|
16
23
|
|
|
17
24
|
class NetboxStatusClient(BaseNetboxClient):
|
|
18
25
|
def _init_response_body_factory(self) -> FactoryProtocol:
|
|
@@ -1,88 +1,36 @@
|
|
|
1
1
|
import ssl
|
|
2
|
-
from collections import defaultdict
|
|
3
2
|
from ipaddress import ip_interface
|
|
4
3
|
from logging import getLogger
|
|
5
|
-
from typing import Any, Optional, List, Union, Dict, cast
|
|
4
|
+
from typing import Any, Optional, List, Union, Dict, cast, Generic, TypeVar
|
|
6
5
|
|
|
7
|
-
from adaptix import P
|
|
8
|
-
from adaptix.conversion import impl_converter, link, link_constant
|
|
9
6
|
from annetbox.v37 import models as api_models
|
|
10
|
-
from annetbox.v37.client_sync import NetboxV37
|
|
11
7
|
|
|
12
|
-
from annet.adapters.netbox.common import models
|
|
13
|
-
from annet.adapters.netbox.common.manufacturer import (
|
|
14
|
-
get_hw, get_breed,
|
|
15
|
-
)
|
|
16
8
|
from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATOR
|
|
17
9
|
from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
|
|
18
|
-
from annet.
|
|
19
|
-
from
|
|
10
|
+
from annet.storage import Storage
|
|
11
|
+
from .adapter import NetboxAdapter
|
|
12
|
+
from .models import IpAddress, Interface, NetboxDevice, Prefix
|
|
20
13
|
|
|
21
14
|
logger = getLogger(__name__)
|
|
15
|
+
NetboxDeviceT = TypeVar("NetboxDeviceT", bound=NetboxDevice)
|
|
16
|
+
InterfaceT = TypeVar("InterfaceT", bound=Interface)
|
|
17
|
+
IpAddressT = TypeVar("IpAddressT", bound=IpAddress)
|
|
18
|
+
PrefixT = TypeVar("PrefixT", bound=Prefix)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseNetboxStorage(
|
|
22
|
+
Storage,
|
|
23
|
+
Generic[
|
|
24
|
+
NetboxDeviceT,
|
|
25
|
+
InterfaceT,
|
|
26
|
+
IpAddressT,
|
|
27
|
+
PrefixT,
|
|
28
|
+
],
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Base class for Netbox storage
|
|
32
|
+
"""
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
@impl_converter(recipe=[
|
|
25
|
-
link(P[api_models.Device].name, P[models.NetboxDevice].hostname),
|
|
26
|
-
link(P[api_models.Device].name, P[models.NetboxDevice].fqdn),
|
|
27
|
-
])
|
|
28
|
-
def extend_device_base(
|
|
29
|
-
device: api_models.Device,
|
|
30
|
-
interfaces: List[models.Interface],
|
|
31
|
-
hw: Optional[HardwareView],
|
|
32
|
-
breed: str,
|
|
33
|
-
storage: Storage,
|
|
34
|
-
) -> models.NetboxDevice:
|
|
35
|
-
...
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def extend_device(
|
|
39
|
-
device: api_models.Device,
|
|
40
|
-
interfaces: List[models.Interface],
|
|
41
|
-
storage: Storage,
|
|
42
|
-
) -> models.NetboxDevice:
|
|
43
|
-
platform_name: str = ""
|
|
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,
|
|
52
|
-
)
|
|
53
|
-
hw = get_hw(
|
|
54
|
-
device.device_type.manufacturer.name,
|
|
55
|
-
device.device_type.model,
|
|
56
|
-
platform_name,
|
|
57
|
-
)
|
|
58
|
-
res = extend_device_base(
|
|
59
|
-
device=device,
|
|
60
|
-
interfaces=interfaces,
|
|
61
|
-
breed=breed,
|
|
62
|
-
hw=hw,
|
|
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
34
|
def __init__(self, opts: Optional[NetboxStorageOpts] = None):
|
|
87
35
|
ctx: Optional[ssl.SSLContext] = None
|
|
88
36
|
url = ""
|
|
@@ -98,12 +46,20 @@ class NetboxStorageV37(Storage):
|
|
|
98
46
|
token = opts.token
|
|
99
47
|
threads = opts.threads
|
|
100
48
|
self.exact_host_filter = opts.exact_host_filter
|
|
101
|
-
|
|
102
|
-
self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx, threads=threads)
|
|
49
|
+
self.netbox = self._init_adapter(url=url, token=token, ssl_context=ctx, threads=threads)
|
|
103
50
|
self._all_fqdns: Optional[list[str]] = None
|
|
104
|
-
self._id_devices: dict[int,
|
|
105
|
-
self._name_devices: dict[str,
|
|
106
|
-
self._short_name_devices: dict[str,
|
|
51
|
+
self._id_devices: dict[int, NetboxDeviceT] = {}
|
|
52
|
+
self._name_devices: dict[str, NetboxDeviceT] = {}
|
|
53
|
+
self._short_name_devices: dict[str, NetboxDeviceT] = {}
|
|
54
|
+
|
|
55
|
+
def _init_adapter(
|
|
56
|
+
self,
|
|
57
|
+
url: str,
|
|
58
|
+
token: str,
|
|
59
|
+
ssl_context: Optional[ssl.SSLContext],
|
|
60
|
+
threads: int,
|
|
61
|
+
) -> NetboxAdapter[NetboxDeviceT, InterfaceT, IpAddressT, PrefixT]:
|
|
62
|
+
raise NotImplementedError()
|
|
107
63
|
|
|
108
64
|
def __enter__(self):
|
|
109
65
|
return self
|
|
@@ -123,10 +79,7 @@ class NetboxStorageV37(Storage):
|
|
|
123
79
|
|
|
124
80
|
def resolve_all_fdnds(self) -> list[str]:
|
|
125
81
|
if self._all_fqdns is None:
|
|
126
|
-
self._all_fqdns =
|
|
127
|
-
d.name
|
|
128
|
-
for d in self.netbox.dcim_all_devices_brief().results
|
|
129
|
-
]
|
|
82
|
+
self._all_fqdns = self.netbox.list_all_fqdns()
|
|
130
83
|
return self._all_fqdns
|
|
131
84
|
|
|
132
85
|
def make_devices(
|
|
@@ -136,7 +89,7 @@ class NetboxStorageV37(Storage):
|
|
|
136
89
|
use_mesh=None,
|
|
137
90
|
preload_extra_fields=False,
|
|
138
91
|
**kwargs,
|
|
139
|
-
) -> List[
|
|
92
|
+
) -> List[NetboxDeviceT]:
|
|
140
93
|
if isinstance(query, list):
|
|
141
94
|
query = NetboxQuery.new(query)
|
|
142
95
|
|
|
@@ -154,106 +107,71 @@ class NetboxStorageV37(Storage):
|
|
|
154
107
|
return devices
|
|
155
108
|
query = NetboxQuery.new(globs)
|
|
156
109
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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]:
|
|
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():
|
|
110
|
+
new_devices = self._load_devices(query)
|
|
111
|
+
self._fill_device_interfaces(new_devices)
|
|
112
|
+
for device in new_devices:
|
|
185
113
|
self._record_device(device)
|
|
114
|
+
return devices + new_devices
|
|
186
115
|
|
|
187
|
-
|
|
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]:
|
|
116
|
+
def _load_devices(self, query: NetboxQuery) -> List[NetboxDeviceT]:
|
|
201
117
|
if not query.globs:
|
|
202
118
|
return []
|
|
203
119
|
query_groups = parse_glob(self.exact_host_filter, query)
|
|
204
|
-
|
|
120
|
+
devices = [
|
|
205
121
|
device
|
|
206
|
-
for device in self.netbox.
|
|
122
|
+
for device in self.netbox.list_devices(query_groups)
|
|
207
123
|
if _match_query(self.exact_host_filter, query, device)
|
|
208
124
|
]
|
|
125
|
+
return devices
|
|
209
126
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
127
|
+
def _fill_device_interfaces(self, devices: list[NetboxDeviceT]) -> None:
|
|
128
|
+
device_mapping = {d.id: d for d in devices}
|
|
129
|
+
interfaces = self.netbox.list_interfaces_by_devices(list(device_mapping))
|
|
130
|
+
for interface in interfaces:
|
|
131
|
+
device_mapping[interface.device.id].interfaces.append(interface)
|
|
132
|
+
self._fill_interface_ipaddress(interfaces)
|
|
133
|
+
|
|
134
|
+
def _fill_interface_ipaddress(self, interfaces: list[InterfaceT]) -> None:
|
|
135
|
+
interface_mapping = {i.id: i for i in interfaces}
|
|
136
|
+
ips = self.netbox.list_ipaddr_by_ifaces(list(interface_mapping))
|
|
137
|
+
for ip in ips:
|
|
138
|
+
interface_mapping[ip.assigned_object_id].ip_addresses.append(ip)
|
|
139
|
+
self._fill_ipaddr_prefixes(ips)
|
|
140
|
+
|
|
141
|
+
def _fill_ipaddr_prefixes(self, ips: list[IpAddressT]) -> None:
|
|
142
|
+
ip_to_cidrs: Dict[str, str] = {ip.address: str(ip_interface(ip.address).network) for ip in ips}
|
|
143
|
+
prefixes = self.netbox.list_ipprefixes(list(ip_to_cidrs.values()))
|
|
144
|
+
cidr_to_prefix: Dict[str, PrefixT] = {x.prefix: x for x in prefixes}
|
|
145
|
+
for ip in ips:
|
|
222
146
|
cidr = ip_to_cidrs[ip.address]
|
|
223
|
-
ip =
|
|
224
|
-
extended_ifaces[ip.assigned_object_id].ip_addresses.append(ip)
|
|
225
|
-
return list(extended_ifaces.values())
|
|
147
|
+
ip.prefix = cidr_to_prefix.get(cidr)
|
|
226
148
|
|
|
227
|
-
def
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return self._extend_interfaces(interfaces.results)
|
|
149
|
+
def _record_device(self, device: NetboxDeviceT):
|
|
150
|
+
self._id_devices[device.id] = device
|
|
151
|
+
self._short_name_devices[device.name] = device
|
|
152
|
+
if not self.exact_host_filter:
|
|
153
|
+
short_name = device.name.split(".")[0]
|
|
154
|
+
self._short_name_devices[short_name] = device
|
|
234
155
|
|
|
235
156
|
def get_device(
|
|
236
157
|
self, obj_id, preload_neighbors=False, use_mesh=None,
|
|
237
158
|
**kwargs,
|
|
238
|
-
) ->
|
|
159
|
+
) -> NetboxDeviceT:
|
|
239
160
|
if obj_id in self._id_devices:
|
|
240
161
|
return self._id_devices[obj_id]
|
|
241
162
|
|
|
242
|
-
device = self.netbox.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
res = extend_device(
|
|
246
|
-
device=device,
|
|
247
|
-
storage=self,
|
|
248
|
-
interfaces=interfaces,
|
|
249
|
-
)
|
|
250
|
-
self._record_device(res)
|
|
251
|
-
return res
|
|
163
|
+
device = self.netbox.get_device(obj_id)
|
|
164
|
+
self._record_device(device)
|
|
165
|
+
return device
|
|
252
166
|
|
|
253
167
|
def flush_perf(self):
|
|
254
168
|
pass
|
|
255
169
|
|
|
256
|
-
def search_connections(
|
|
170
|
+
def search_connections(
|
|
171
|
+
self,
|
|
172
|
+
device: NetboxDeviceT,
|
|
173
|
+
neighbor: NetboxDeviceT,
|
|
174
|
+
) -> list[tuple[InterfaceT, InterfaceT]]:
|
|
257
175
|
if device.storage is not self:
|
|
258
176
|
raise ValueError("device does not belong to this storage")
|
|
259
177
|
if neighbor.storage is not self:
|