annet 0.16.8__tar.gz → 0.16.10__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.

Files changed (171) hide show
  1. {annet-0.16.8/annet.egg-info → annet-0.16.10}/PKG-INFO +3 -1
  2. annet-0.16.10/README.md +62 -0
  3. annet-0.16.10/annet/adapters/fetchers/stub/fetcher.py +19 -0
  4. annet-0.16.10/annet/adapters/file/provider.py +226 -0
  5. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/manufacturer.py +2 -0
  6. annet-0.16.10/annet/adapters/netbox/common/models.py +276 -0
  7. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/v37/storage.py +31 -3
  8. {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/views/hardware.py +31 -0
  9. annet-0.16.10/annet/bgp_models.py +266 -0
  10. annet-0.16.10/annet/configs/context.yml +15 -0
  11. {annet-0.16.8 → annet-0.16.10}/annet/configs/logging.yaml +1 -0
  12. {annet-0.16.8 → annet-0.16.10}/annet/generators/__init__.py +18 -4
  13. {annet-0.16.8 → annet-0.16.10}/annet/generators/jsonfragment.py +13 -12
  14. annet-0.16.10/annet/mesh/__init__.py +16 -0
  15. annet-0.16.10/annet/mesh/basemodel.py +180 -0
  16. annet-0.16.10/annet/mesh/device_models.py +62 -0
  17. annet-0.16.10/annet/mesh/executor.py +248 -0
  18. annet-0.16.10/annet/mesh/match_args.py +165 -0
  19. annet-0.16.10/annet/mesh/models_converter.py +84 -0
  20. annet-0.16.10/annet/mesh/peer_models.py +98 -0
  21. annet-0.16.10/annet/mesh/registry.py +212 -0
  22. annet-0.16.10/annet/rulebook/nexus/__init__.py +0 -0
  23. annet-0.16.10/annet/rulebook/routeros/__init__.py +0 -0
  24. annet-0.16.10/annet/rulebook/routeros/file.py +5 -0
  25. annet-0.16.10/annet/rulebook/texts/pc.deploy +0 -0
  26. {annet-0.16.8 → annet-0.16.10}/annet/storage.py +41 -2
  27. {annet-0.16.8 → annet-0.16.10/annet.egg-info}/PKG-INFO +3 -1
  28. {annet-0.16.8 → annet-0.16.10}/annet.egg-info/SOURCES.txt +20 -1
  29. {annet-0.16.8 → annet-0.16.10}/annet.egg-info/requires.txt +3 -0
  30. annet-0.16.10/annet_generators/__init__.py +0 -0
  31. {annet-0.16.8 → annet-0.16.10}/annet_generators/example/__init__.py +1 -3
  32. annet-0.16.10/annet_generators/mesh_example/__init__.py +9 -0
  33. annet-0.16.10/annet_generators/mesh_example/bgp.py +43 -0
  34. annet-0.16.10/annet_generators/mesh_example/mesh_logic.py +28 -0
  35. {annet-0.16.8 → annet-0.16.10}/setup.py +3 -0
  36. annet-0.16.8/README.md +0 -176
  37. annet-0.16.8/annet/adapters/netbox/common/models.py +0 -171
  38. annet-0.16.8/annet/configs/context.yml +0 -25
  39. {annet-0.16.8 → annet-0.16.10}/AUTHORS +0 -0
  40. {annet-0.16.8 → annet-0.16.10}/LICENSE +0 -0
  41. {annet-0.16.8 → annet-0.16.10}/MANIFEST.in +0 -0
  42. {annet-0.16.8 → annet-0.16.10}/annet/__init__.py +0 -0
  43. {annet-0.16.8 → annet-0.16.10}/annet/adapters/__init__.py +0 -0
  44. {annet-0.16.8/annet/adapters/netbox → annet-0.16.10/annet/adapters/fetchers}/__init__.py +0 -0
  45. {annet-0.16.8/annet/adapters/netbox/common → annet-0.16.10/annet/adapters/fetchers/stub}/__init__.py +0 -0
  46. {annet-0.16.8/annet/adapters/netbox/v24 → annet-0.16.10/annet/adapters/file}/__init__.py +0 -0
  47. {annet-0.16.8/annet/adapters/netbox/v37 → annet-0.16.10/annet/adapters/netbox}/__init__.py +0 -0
  48. {annet-0.16.8/annet/annlib/netdev → annet-0.16.10/annet/adapters/netbox/common}/__init__.py +0 -0
  49. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/client.py +0 -0
  50. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/query.py +0 -0
  51. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/status_client.py +0 -0
  52. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/common/storage_opts.py +0 -0
  53. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/provider.py +0 -0
  54. {annet-0.16.8/annet/annlib/netdev/views → annet-0.16.10/annet/adapters/netbox/v24}/__init__.py +0 -0
  55. {annet-0.16.8 → annet-0.16.10}/annet/adapters/netbox/v24/storage.py +0 -0
  56. {annet-0.16.8/annet/annlib/rbparser → annet-0.16.10/annet/adapters/netbox/v37}/__init__.py +0 -0
  57. {annet-0.16.8 → annet-0.16.10}/annet/annet.py +0 -0
  58. {annet-0.16.8 → annet-0.16.10}/annet/annlib/__init__.py +0 -0
  59. {annet-0.16.8 → annet-0.16.10}/annet/annlib/command.py +0 -0
  60. {annet-0.16.8 → annet-0.16.10}/annet/annlib/diff.py +0 -0
  61. {annet-0.16.8 → annet-0.16.10}/annet/annlib/errors.py +0 -0
  62. {annet-0.16.8 → annet-0.16.10}/annet/annlib/filter_acl.py +0 -0
  63. {annet-0.16.8 → annet-0.16.10}/annet/annlib/jsontools.py +0 -0
  64. {annet-0.16.8 → annet-0.16.10}/annet/annlib/lib.py +0 -0
  65. {annet-0.16.8/annet/annlib/rulebook → annet-0.16.10/annet/annlib/netdev}/__init__.py +0 -0
  66. {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/db.py +0 -0
  67. {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/devdb/__init__.py +0 -0
  68. {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  69. {annet-0.16.8/annet/generators/common → annet-0.16.10/annet/annlib/netdev/views}/__init__.py +0 -0
  70. {annet-0.16.8 → annet-0.16.10}/annet/annlib/netdev/views/dump.py +0 -0
  71. {annet-0.16.8 → annet-0.16.10}/annet/annlib/output.py +0 -0
  72. {annet-0.16.8 → annet-0.16.10}/annet/annlib/patching.py +0 -0
  73. {annet-0.16.8/annet/rulebook/arista → annet-0.16.10/annet/annlib/rbparser}/__init__.py +0 -0
  74. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/acl.py +0 -0
  75. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/deploying.py +0 -0
  76. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/ordering.py +0 -0
  77. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/platform.py +0 -0
  78. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rbparser/syntax.py +0 -0
  79. {annet-0.16.8/annet/rulebook/b4com → annet-0.16.10/annet/annlib/rulebook}/__init__.py +0 -0
  80. {annet-0.16.8 → annet-0.16.10}/annet/annlib/rulebook/common.py +0 -0
  81. {annet-0.16.8 → annet-0.16.10}/annet/annlib/tabparser.py +0 -0
  82. {annet-0.16.8 → annet-0.16.10}/annet/annlib/types.py +0 -0
  83. {annet-0.16.8 → annet-0.16.10}/annet/api/__init__.py +0 -0
  84. {annet-0.16.8 → annet-0.16.10}/annet/argparse.py +0 -0
  85. {annet-0.16.8 → annet-0.16.10}/annet/cli.py +0 -0
  86. {annet-0.16.8 → annet-0.16.10}/annet/cli_args.py +0 -0
  87. {annet-0.16.8 → annet-0.16.10}/annet/connectors.py +0 -0
  88. {annet-0.16.8 → annet-0.16.10}/annet/deploy.py +0 -0
  89. {annet-0.16.8 → annet-0.16.10}/annet/diff.py +0 -0
  90. {annet-0.16.8 → annet-0.16.10}/annet/executor.py +0 -0
  91. {annet-0.16.8 → annet-0.16.10}/annet/filtering.py +0 -0
  92. {annet-0.16.8 → annet-0.16.10}/annet/gen.py +0 -0
  93. {annet-0.16.8 → annet-0.16.10}/annet/generators/base.py +0 -0
  94. {annet-0.16.8/annet/rulebook/cisco → annet-0.16.10/annet/generators/common}/__init__.py +0 -0
  95. {annet-0.16.8 → annet-0.16.10}/annet/generators/common/initial.py +0 -0
  96. {annet-0.16.8 → annet-0.16.10}/annet/generators/entire.py +0 -0
  97. {annet-0.16.8 → annet-0.16.10}/annet/generators/exceptions.py +0 -0
  98. {annet-0.16.8 → annet-0.16.10}/annet/generators/partial.py +0 -0
  99. {annet-0.16.8 → annet-0.16.10}/annet/generators/perf.py +0 -0
  100. {annet-0.16.8 → annet-0.16.10}/annet/generators/ref.py +0 -0
  101. {annet-0.16.8 → annet-0.16.10}/annet/generators/result.py +0 -0
  102. {annet-0.16.8 → annet-0.16.10}/annet/hardware.py +0 -0
  103. {annet-0.16.8 → annet-0.16.10}/annet/implicit.py +0 -0
  104. {annet-0.16.8 → annet-0.16.10}/annet/lib.py +0 -0
  105. {annet-0.16.8 → annet-0.16.10}/annet/output.py +0 -0
  106. {annet-0.16.8 → annet-0.16.10}/annet/parallel.py +0 -0
  107. {annet-0.16.8 → annet-0.16.10}/annet/patching.py +0 -0
  108. {annet-0.16.8 → annet-0.16.10}/annet/reference.py +0 -0
  109. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/__init__.py +0 -0
  110. {annet-0.16.8/annet/rulebook/huawei → annet-0.16.10/annet/rulebook/arista}/__init__.py +0 -0
  111. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/arista/iface.py +0 -0
  112. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/__init__.py +0 -0
  113. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/ap_env.py +0 -0
  114. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/aruba/misc.py +0 -0
  115. {annet-0.16.8/annet/rulebook/nexus → annet-0.16.10/annet/rulebook/b4com}/__init__.py +0 -0
  116. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/b4com/file.py +0 -0
  117. {annet-0.16.8/annet_generators → annet-0.16.10/annet/rulebook/cisco}/__init__.py +0 -0
  118. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/iface.py +0 -0
  119. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/misc.py +0 -0
  120. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/cisco/vlandb.py +0 -0
  121. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/common.py +0 -0
  122. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/deploying.py +0 -0
  123. /annet-0.16.8/annet/rulebook/texts/pc.deploy → /annet-0.16.10/annet/rulebook/huawei/__init__.py +0 -0
  124. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/aaa.py +0 -0
  125. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/bgp.py +0 -0
  126. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/iface.py +0 -0
  127. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/misc.py +0 -0
  128. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/huawei/vlandb.py +0 -0
  129. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/juniper/__init__.py +0 -0
  130. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/nexus/iface.py +0 -0
  131. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/patching.py +0 -0
  132. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/ribbon/__init__.py +0 -0
  133. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.deploy +0 -0
  134. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.order +0 -0
  135. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/arista.rul +0 -0
  136. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.deploy +0 -0
  137. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.order +0 -0
  138. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/aruba.rul +0 -0
  139. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.deploy +0 -0
  140. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.order +0 -0
  141. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/b4com.rul +0 -0
  142. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.deploy +0 -0
  143. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.order +0 -0
  144. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/cisco.rul +0 -0
  145. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.deploy +0 -0
  146. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.order +0 -0
  147. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/huawei.rul +0 -0
  148. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/juniper.rul +0 -0
  149. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.deploy +0 -0
  150. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.order +0 -0
  151. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nexus.rul +0 -0
  152. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/nokia.rul +0 -0
  153. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.deploy +0 -0
  154. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.order +0 -0
  155. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/optixtrans.rul +0 -0
  156. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/pc.order +0 -0
  157. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/pc.rul +0 -0
  158. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/ribbon.deploy +0 -0
  159. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/ribbon.rul +0 -0
  160. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/routeros.order +0 -0
  161. {annet-0.16.8 → annet-0.16.10}/annet/rulebook/texts/routeros.rul +0 -0
  162. {annet-0.16.8 → annet-0.16.10}/annet/tabparser.py +0 -0
  163. {annet-0.16.8 → annet-0.16.10}/annet/text_term_format.py +0 -0
  164. {annet-0.16.8 → annet-0.16.10}/annet/tracing.py +0 -0
  165. {annet-0.16.8 → annet-0.16.10}/annet/types.py +0 -0
  166. {annet-0.16.8 → annet-0.16.10}/annet.egg-info/dependency_links.txt +0 -0
  167. {annet-0.16.8 → annet-0.16.10}/annet.egg-info/entry_points.txt +0 -0
  168. {annet-0.16.8 → annet-0.16.10}/annet.egg-info/top_level.txt +0 -0
  169. {annet-0.16.8 → annet-0.16.10}/annet_generators/example/lldp.py +0 -0
  170. {annet-0.16.8 → annet-0.16.10}/requirements.txt +0 -0
  171. {annet-0.16.8 → annet-0.16.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.16.8
3
+ Version: 0.16.10
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -23,3 +23,5 @@ Requires-Dist: aiohttp>=3.8.4
23
23
  Requires-Dist: yarl>=1.8.2
24
24
  Requires-Dist: adaptix==3.0.0b7
25
25
  Requires-Dist: dataclass-rest==0.4
26
+ Provides-Extra: netbox
27
+ Requires-Dist: annetbox[sync]>=0.1.8; extra == "netbox"
@@ -0,0 +1,62 @@
1
+ # Annet - configuration generation and deploying utility for network equipment
2
+
3
+ Annet is a configuration generator that can translate differences between old and new configurations into sequnce of commands. This feature is vital for CLI-based devices, such as Huawei, Cisco IOS, Cisco NX-OS, Juniper. Devices configured via separate config files, Linux, FreeBSD and Cumulus are also supported.
4
+
5
+ It works this way. Annet `gen`erates configuration for a device by running Python code, which usually goes to the Network Source of Truth, like NetBox. Annet then gets the `diff`erence by getting the configuration from the device and comparing it. Finally, Annet translates the difference into a sequence of commands, called a `patch`. After `deploy`ing these commands, the diff will be empty.
6
+
7
+ Annet has a number of modes (subcommands):
8
+
9
+ - ```annet gen``` - generates the entire config for the specified devices or specified parts of it
10
+ - ```annet diff``` - first does gen and then builds diff with current config version
11
+ - ```annet patch``` - first does diff and then generates a list of commands to apply diff on the device
12
+ - ```annet deploy``` - first does patch and then deploys it to the device
13
+
14
+ Usage help can be obtained by calling ```annet -h``` or for a specific command, such as ```annet gen -h```.
15
+
16
+ <img src="https://github.com/annetutil/annet/blob/main/docs/_static/annet_demo.gif?raw=true" width="800" />
17
+
18
+ ## Configuration
19
+
20
+ The path to the configuration file is searched in following order:
21
+ - `ANN_CONTEXT_CONFIG_PATH` env.
22
+ - `~/.annet/context.yml`.
23
+ - `annet/configs/context.yml`.
24
+
25
+ Config example:
26
+
27
+ ```yaml
28
+ generators:
29
+ default:
30
+ - my_annet_generators.example
31
+
32
+ storage:
33
+ default:
34
+ adapter: annet.adapters.file.provider
35
+ params:
36
+ path: /path/to/file
37
+
38
+ context:
39
+ default:
40
+ generators: default
41
+ storage: default
42
+
43
+ selected_context: default
44
+ ```
45
+
46
+ Environment variable `ANN_SELECTED_CONTEXT` can be used to override `selected_context` parameter.
47
+
48
+ ## Building doc
49
+
50
+ 1. Install dependencies:
51
+
52
+ ```shell
53
+ pip install -r requirements-doc.txt
54
+ ```
55
+
56
+ 2. Build
57
+
58
+ ```shell
59
+ sphinx-build -M html docs docs-build
60
+ ```
61
+
62
+ 3. Open rendered html in browser [docs-build/html/index.html](docs-build/html/index.html)
@@ -0,0 +1,19 @@
1
+ from annet.deploy import Fetcher
2
+ from annet.connectors import AdapterWithConfig
3
+ from typing import Dict, List, Any
4
+ from annet.storage import Device
5
+
6
+
7
+ class StubFetcher(Fetcher, AdapterWithConfig):
8
+ @classmethod
9
+ def with_config(cls, **kwargs: Dict[str, Any]) -> Fetcher:
10
+ return cls(**kwargs)
11
+
12
+ def fetch_packages(self, devices: List[Device],
13
+ processes: int = 1, max_slots: int = 0):
14
+ raise NotImplementedError()
15
+
16
+ def fetch(self, devices: List[Device],
17
+ files_to_download: Dict[str, List[str]] = None,
18
+ processes: int = 1, max_slots: int = 0):
19
+ raise NotImplementedError()
@@ -0,0 +1,226 @@
1
+ from annet.annlib.netdev.views.dump import DumpableView
2
+ from annet.storage import Query
3
+ from dataclasses import dataclass, fields
4
+ from typing import List, Iterable, Optional, Any
5
+ from annet.storage import StorageProvider, Storage
6
+ from annet.connectors import AdapterWithName
7
+ from annet.storage import Device as DeviceCls
8
+ from annet.annlib.netdev.views.hardware import vendor_to_hw, HardwareView
9
+ import yaml
10
+
11
+
12
+ @dataclass
13
+ class Interface(DumpableView):
14
+ name: str
15
+ description: str
16
+ enabled: bool = True
17
+
18
+ @property
19
+ def _dump__list_key(self):
20
+ return self.name
21
+
22
+
23
+ @dataclass
24
+ class DeviceStorage:
25
+ fqdn: str
26
+ vendor: str
27
+ hostname: Optional[str] = None
28
+ serial: Optional[str] = None
29
+ id: Optional[str] = None
30
+ interfaces: Optional[list[Interface]] = None
31
+ storage: Optional[Storage] = None
32
+
33
+ def __post_init__(self):
34
+ if not self.id:
35
+ self.id = self.fqdn
36
+ if not self.hostname:
37
+ self.hostname = self.fqdn.split(".")[0]
38
+ hw = vendor_to_hw(self.vendor)
39
+ if not hw:
40
+ raise Exception("unknown vendor")
41
+ self.hw = hw
42
+ if isinstance(self.interfaces, list):
43
+ interfaces = []
44
+ for iface in self.interfaces:
45
+ try:
46
+ interfaces.append(Interface(**iface))
47
+ except Exception as e:
48
+ raise Exception("unable to parse %s as Interface %s" % (iface, e))
49
+ self.interfaces = interfaces
50
+
51
+ def set_storage(self, storage: Storage):
52
+ self.storage = storage
53
+
54
+
55
+ @dataclass
56
+ class Device(DeviceCls, DumpableView):
57
+ dev: DeviceStorage
58
+
59
+ @property
60
+ def hostname(self) -> str:
61
+ return self.dev.hostname
62
+
63
+ @property
64
+ def fqdn(self) -> str:
65
+ return self.dev.fqdn
66
+
67
+ @property
68
+ def id(self):
69
+ return self.dev.id
70
+
71
+ def __hash__(self):
72
+ return hash((self.id, type(self)))
73
+
74
+ def __eq__(self, other):
75
+ return type(self) is type(other) and self.fqdn == other.fqdn and self.vendor == other.vendor
76
+
77
+ def is_pc(self) -> bool:
78
+ return False
79
+
80
+ @property
81
+ def storage(self) -> Storage:
82
+ return self
83
+
84
+ @property
85
+ def hw(self) -> HardwareView:
86
+ return self.dev.hw
87
+
88
+ @property
89
+ def breed(self) -> str:
90
+ return self.dev.hw.vendor
91
+
92
+ @property
93
+ def neighbours_ids(self):
94
+ pass
95
+
96
+
97
+ @dataclass
98
+ class Devices:
99
+ devices: list[Device]
100
+
101
+ def __post_init__(self):
102
+ if isinstance(self.devices, list):
103
+ devices = []
104
+ for dev in self.devices:
105
+ try:
106
+ devices.append(Device(dev=DeviceStorage(**dev)))
107
+ except Exception as e:
108
+ raise Exception("unable to parse %s as Device %s" % (dev, e))
109
+ self.devices = devices
110
+
111
+
112
+ class Provider(StorageProvider, AdapterWithName):
113
+ def storage(self):
114
+ return storage_factory
115
+
116
+ def opts(self):
117
+ return StorageOpts
118
+
119
+ def query(self):
120
+ return Query
121
+
122
+ @classmethod
123
+ def name(cls) -> str:
124
+ return "file"
125
+
126
+
127
+ @dataclass
128
+ class Query(Query):
129
+ query: List[str]
130
+
131
+ @classmethod
132
+ def new(cls, query: str | Iterable[str], hosts_range: Optional[slice] = None) -> "Query":
133
+ if hosts_range is not None:
134
+ raise ValueError("host_range is not supported")
135
+ return cls(query=list(query))
136
+
137
+ @property
138
+ def globs(self):
139
+ return self.query
140
+
141
+ def is_empty(self) -> bool:
142
+ return len(self.query) == 0
143
+
144
+
145
+ class StorageOpts:
146
+ def __init__(self, path: str):
147
+ self.path = path
148
+
149
+ @classmethod
150
+ def parse_params(cls, conf_params: Optional[dict[str, str]], cli_opts: Any):
151
+ path = conf_params.get("path")
152
+ if not path:
153
+ raise Exception("empty path")
154
+ return cls(path=path)
155
+
156
+
157
+ def storage_factory(opts: StorageOpts) -> Storage:
158
+ return FS(opts)
159
+
160
+
161
+ class FS(Storage):
162
+ def __init__(self, opts: StorageOpts):
163
+ self.opts = opts
164
+ self.inventory: Devices = read_inventory(opts.path, self)
165
+
166
+ def __enter__(self):
167
+ return self
168
+
169
+ def __exit__(self, _, __, ___):
170
+ pass
171
+
172
+ def resolve_object_ids_by_query(self, query: Query) -> list[str]:
173
+ result = filter_query(self.inventory.devices, query)
174
+ return [dev.fqdn for dev in result]
175
+
176
+ def resolve_fdnds_by_query(self, query: Query) -> list[str]:
177
+ result = filter_query(self.inventory.devices, query)
178
+ return [dev.fqdn for dev in result]
179
+
180
+ def make_devices(
181
+ self,
182
+ query: Query | list,
183
+ preload_neighbors=False,
184
+ use_mesh=None,
185
+ preload_extra_fields=False,
186
+ **kwargs,
187
+ ) -> list[Device]:
188
+ if isinstance(query, list):
189
+ query = Query.new(query)
190
+ result = filter_query(self.inventory.devices, query)
191
+ return result
192
+
193
+ def get_device(self, obj_id: str, preload_neighbors=False, use_mesh=None, **kwargs) -> Device:
194
+ result = filter_query(self.inventory.devices, Query.new(obj_id))
195
+ if not result:
196
+ raise Exception("not found")
197
+ return result[0]
198
+
199
+ def flush_perf(self):
200
+ pass
201
+
202
+
203
+ def filter_query(devices: list[Device], query: Query) -> list[Device]:
204
+ result: list[Device] = []
205
+ for dev in devices:
206
+ if dev.fqdn in query.query:
207
+ result.append(dev)
208
+ return result
209
+
210
+
211
+ def read_inventory(path: str, storage: Storage) -> Devices:
212
+ with open(path, "r") as f:
213
+ data = f.read()
214
+ file_data = yaml.load(data, Loader=yaml.BaseLoader)
215
+ res = dataclass_from_dict(Devices, file_data)
216
+ for dev in res.devices:
217
+ dev.dev.set_storage(storage)
218
+ return res
219
+
220
+
221
+ def dataclass_from_dict(klass: type, d: dict[str, Any]):
222
+ try:
223
+ fieldtypes = {f.name: f.type for f in fields(klass)}
224
+ except TypeError:
225
+ return d
226
+ return klass(**{f: dataclass_from_dict(fieldtypes[f], d[f]) for f in d})
@@ -51,4 +51,6 @@ def get_breed(manufacturer: str, model: str):
51
51
  return "bcom-os"
52
52
  elif manufacturer == "MikroTik":
53
53
  return "routeros"
54
+ elif manufacturer == "PC":
55
+ return "pc"
54
56
  return ""
@@ -0,0 +1,276 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime, timezone
3
+ from ipaddress import ip_interface, IPv6Interface
4
+ from typing import List, Optional, Any, Dict, Sequence, Callable
5
+
6
+ from annet.annlib.netdev.views.dump import DumpableView
7
+ from annet.annlib.netdev.views.hardware import HardwareView, lag_name, svi_name
8
+ from annet.storage import Storage
9
+
10
+
11
+ @dataclass
12
+ class Entity(DumpableView):
13
+ id: int
14
+ name: str
15
+
16
+ @property
17
+ def _dump__list_key(self):
18
+ return self.name
19
+
20
+
21
+ @dataclass
22
+ class Label:
23
+ value: str
24
+ label: str
25
+
26
+
27
+ @dataclass
28
+ class IpFamily:
29
+ value: int
30
+ label: str
31
+
32
+
33
+ @dataclass
34
+ class DeviceType:
35
+ id: int
36
+ manufacturer: Entity
37
+ model: str
38
+
39
+
40
+ @dataclass
41
+ class DeviceIp(DumpableView):
42
+ id: int
43
+ display: str
44
+ address: str
45
+ family: int
46
+
47
+ @property
48
+ def _dump__list_key(self):
49
+ return self.address
50
+
51
+
52
+ @dataclass
53
+ class Prefix(DumpableView):
54
+ id: int
55
+ prefix: str
56
+ site: Optional[Entity]
57
+ vrf: Optional[Entity]
58
+ tenant: Optional[Entity]
59
+ vlan: Optional[Entity]
60
+ role: Optional[Entity]
61
+ status: Label
62
+ is_pool: bool
63
+ custom_fields: dict[str, Any]
64
+ created: datetime
65
+ last_updated: datetime
66
+ description: Optional[str] = ""
67
+
68
+ @property
69
+ def _dump__list_key(self):
70
+ return self.prefix
71
+
72
+
73
+ @dataclass
74
+ class IpAddress(DumpableView):
75
+ id: int
76
+ assigned_object_id: int
77
+ display: str
78
+ family: IpFamily
79
+ address: str
80
+ status: Label
81
+ tags: List[Entity]
82
+ created: datetime
83
+ last_updated: datetime
84
+ prefix: Optional[Prefix] = None
85
+ vrf: Optional[Entity] = None
86
+
87
+ @property
88
+ def _dump__list_key(self):
89
+ return self.address
90
+
91
+
92
+ @dataclass
93
+ class InterfaceConnectedEndpoint(Entity):
94
+ device: Entity
95
+
96
+
97
+ @dataclass
98
+ class InterfaceType:
99
+ value: str
100
+ label: str
101
+
102
+
103
+ @dataclass
104
+ class InterfaceMode:
105
+ value: str
106
+ label: str
107
+
108
+
109
+ @dataclass
110
+ class InterfaceVlan(Entity):
111
+ vid: int
112
+
113
+
114
+ @dataclass
115
+ class Interface(Entity):
116
+ device: Entity
117
+ enabled: bool
118
+ description: str
119
+ type: InterfaceType
120
+ connected_endpoints: Optional[list[InterfaceConnectedEndpoint]]
121
+ mode: Optional[InterfaceMode]
122
+ untagged_vlan: Optional[InterfaceVlan]
123
+ tagged_vlans: Optional[List[InterfaceVlan]]
124
+ display: str = ""
125
+ ip_addresses: List[IpAddress] = field(default_factory=list)
126
+ vrf: Optional[Entity] = None
127
+ mtu: int | None = None
128
+ lag: Entity | None = None
129
+ lag_min_links: int | None = None
130
+
131
+ def add_addr(self, address_mask: str, vrf: str | None) -> None:
132
+ addr = ip_interface(address_mask)
133
+ if vrf is None:
134
+ vrf_obj = None
135
+ else:
136
+ vrf_obj = Entity(id=0, name=vrf)
137
+
138
+ if isinstance(addr, IPv6Interface):
139
+ family = IpFamily(value=6, label="IPv6")
140
+ else:
141
+ family = IpFamily(value=4, label="IPv4")
142
+ self.ip_addresses.append(IpAddress(
143
+ id=0,
144
+ display=address_mask,
145
+ address=address_mask,
146
+ vrf=vrf_obj,
147
+ prefix=None,
148
+ family=family,
149
+ created=datetime.now(timezone.utc),
150
+ last_updated=datetime.now(timezone.utc),
151
+ tags=[],
152
+ status=Label(value="active", label="Active"),
153
+ assigned_object_id=self.id,
154
+ ))
155
+
156
+
157
+ @dataclass
158
+ class NetboxDevice(Entity):
159
+ url: str
160
+ storage: Storage
161
+
162
+ display: str
163
+ device_type: DeviceType
164
+ device_role: Entity
165
+ tenant: Optional[Entity]
166
+ platform: Optional[Entity]
167
+ serial: str
168
+ asset_tag: Optional[str]
169
+ site: Entity
170
+ rack: Optional[Entity]
171
+ position: Optional[float]
172
+ face: Optional[Label]
173
+ status: Label
174
+ primary_ip: Optional[DeviceIp]
175
+ primary_ip4: Optional[DeviceIp]
176
+ primary_ip6: Optional[DeviceIp]
177
+ tags: List[Entity]
178
+ custom_fields: Dict[str, Any]
179
+ created: datetime
180
+ last_updated: datetime
181
+
182
+ fqdn: str
183
+ hostname: str
184
+ hw: Optional[HardwareView]
185
+ breed: str
186
+
187
+ interfaces: List[Interface]
188
+ neighbours: Optional[List["NetboxDevice"]]
189
+
190
+ # compat
191
+ @property
192
+ def neighbours_fqdns(self) -> list[str]:
193
+ if not self.neighbours:
194
+ return []
195
+ return [dev.fqdn for dev in self.neighbours]
196
+
197
+ @property
198
+ def neighbours_ids(self):
199
+ if not self.neighbours:
200
+ return []
201
+ return [dev.id for dev in self.neighbours]
202
+
203
+ def __hash__(self):
204
+ return hash((self.id, type(self)))
205
+
206
+ def __eq__(self, other):
207
+ return type(self) is type(other) and self.url == other.url
208
+
209
+ def is_pc(self) -> bool:
210
+ return self.device_type.manufacturer.name == "Mellanox" or self.breed == "pc"
211
+
212
+ def _make_interface(self, name: str, type: InterfaceType) -> Interface:
213
+ return Interface(
214
+ name=name,
215
+ device=self,
216
+ enabled=True,
217
+ description="",
218
+ type=type,
219
+ id=0,
220
+ vrf=None,
221
+ display=name,
222
+ untagged_vlan=None,
223
+ tagged_vlans=[],
224
+ ip_addresses=[],
225
+ connected_endpoints=[],
226
+ mode=None,
227
+ )
228
+
229
+ def _lag_name(self, lag: int) -> str:
230
+ return lag_name(self.hw, lag)
231
+
232
+ def make_lag(self, lag: int, ports: Sequence[str], lag_min_links: int | None) -> Interface:
233
+ new_name = self._lag_name(lag)
234
+ for target_interface in self.interfaces:
235
+ if target_interface.name == new_name:
236
+ return target_interface
237
+ lag_interface = self._make_interface(
238
+ name=new_name,
239
+ type=InterfaceType(value="lag", label="Link Aggregation Group (LAG)"),
240
+ )
241
+ lag_interface.lag_min_links = lag_min_links
242
+ for interface in self.interfaces:
243
+ if interface.name in ports:
244
+ interface.lag = lag_interface
245
+ self.interfaces.append(lag_interface)
246
+ return lag_interface
247
+
248
+ def _svi_name(self, svi: int) -> str:
249
+ return svi_name(self.hw, svi)
250
+
251
+ def add_svi(self, svi: int) -> Interface:
252
+ name = self._svi_name(svi)
253
+ for interface in self.interfaces:
254
+ if interface.name == name:
255
+ return interface
256
+ interface = self._make_interface(
257
+ name=name,
258
+ type=InterfaceType("virtual", "Virtual")
259
+ )
260
+ self.interfaces.append(interface)
261
+ return interface
262
+
263
+ def _subif_name(self, interface: str, subif: int) -> str:
264
+ return f"{interface}.{subif}"
265
+
266
+ def add_subif(self, interface: str, subif: int) -> Interface:
267
+ name = self._subif_name(interface, subif)
268
+ for target_port in self.interfaces:
269
+ if target_port.name == name:
270
+ return target_port
271
+ target_port = self._make_interface(
272
+ name=name,
273
+ type=InterfaceType("virtual", "Virtual")
274
+ )
275
+ self.interfaces.append(target_port)
276
+ return target_port
@@ -15,8 +15,7 @@ from annet.adapters.netbox.common.manufacturer import (
15
15
  from annet.adapters.netbox.common.query import NetboxQuery
16
16
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
17
17
  from annet.annlib.netdev.views.hardware import HardwareView
18
- from annet.storage import Storage
19
-
18
+ from annet.storage import Storage, Device, Interface
20
19
 
21
20
  logger = getLogger(__name__)
22
21
 
@@ -68,7 +67,9 @@ def extend_device(
68
67
  return res
69
68
 
70
69
 
71
- @impl_converter
70
+ @impl_converter(
71
+ recipe=[link_constant(P[models.Interface].lag_min_links, value=None)],
72
+ )
72
73
  def extend_interface(
73
74
  interface: api_models.Interface,
74
75
  ip_addresses: List[models.IpAddress],
@@ -89,6 +90,7 @@ class NetboxStorageV37(Storage):
89
90
  url=opts.url,
90
91
  token=opts.token,
91
92
  )
93
+ self._all_fqdns: Optional[list[str]] = None
92
94
 
93
95
  def __enter__(self):
94
96
  return self
@@ -106,6 +108,14 @@ class NetboxStorageV37(Storage):
106
108
  d.name for d in self._load_devices(query)
107
109
  ]
108
110
 
111
+ def resolve_all_fdnds(self) -> list[str]:
112
+ if self._all_fqdns is None:
113
+ self._all_fqdns = [
114
+ d.name
115
+ for d in self.netbox.dcim_all_devices().results
116
+ ]
117
+ return self._all_fqdns
118
+
109
119
  def make_devices(
110
120
  self,
111
121
  query: Union[NetboxQuery, list],
@@ -218,6 +228,24 @@ class NetboxStorageV37(Storage):
218
228
  def flush_perf(self):
219
229
  pass
220
230
 
231
+ def search_connections(self, device: Device, neighbor: Device) -> list[tuple[Interface, Interface]]:
232
+ if device.storage is not self:
233
+ raise ValueError("device does not belong to this storage")
234
+ if neighbor.storage is not self:
235
+ raise ValueError("neighbor does not belong to this storage")
236
+ # both devices are NetboxDevice if they are loaded from this storage
237
+ res = []
238
+ for local_port in device.interfaces:
239
+ if not local_port.connected_endpoints:
240
+ continue
241
+ for endpoint in local_port.connected_endpoints:
242
+ if endpoint.device.id == neighbor.id:
243
+ for remote_port in neighbor.interfaces:
244
+ if remote_port.name == endpoint.name:
245
+ res.append((local_port, remote_port))
246
+ break
247
+ return res
248
+
221
249
 
222
250
  def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
223
251
  for subquery in query.globs:
@@ -123,3 +123,34 @@ def vendor_to_hw(vendor):
123
123
  None,
124
124
  )
125
125
  return hw
126
+
127
+
128
+ def lag_name(hw: HardwareView, nlagg: int) -> str:
129
+ if hw.Huawei:
130
+ return f"Eth-Trunk{nlagg}"
131
+ if hw.Cisco:
132
+ return f"port-channel{nlagg}"
133
+ if hw.Nexus:
134
+ return f"port-channel{nlagg}"
135
+ if hw.Arista:
136
+ return f"Port-Channel{nlagg}"
137
+ if hw.Juniper:
138
+ return f"ae{nlagg}"
139
+ if hw.Nokia:
140
+ return f"lag-{nlagg}"
141
+ if hw.PC.Whitebox:
142
+ return f"bond{nlagg}"
143
+ if hw.PC:
144
+ return f"lagg{nlagg}"
145
+ if hw.Nokia:
146
+ return f"lagg-{nlagg}"
147
+ raise NotImplementedError(hw)
148
+
149
+
150
+ def svi_name(hw: HardwareView, num: int) -> str:
151
+ if hw.Juniper:
152
+ return f"irb.{num}"
153
+ elif hw.Huawei:
154
+ return f"Vlanif{num}"
155
+ else:
156
+ return f"vlan{num}"