annet 0.16.7__tar.gz → 0.16.9__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 (169) hide show
  1. {annet-0.16.7/annet.egg-info → annet-0.16.9}/PKG-INFO +3 -1
  2. {annet-0.16.7 → annet-0.16.9}/README.md +70 -10
  3. annet-0.16.9/annet/adapters/fetchers/stub/fetcher.py +19 -0
  4. annet-0.16.9/annet/adapters/file/provider.py +226 -0
  5. annet-0.16.9/annet/adapters/netbox/common/models.py +276 -0
  6. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/v37/storage.py +31 -3
  7. {annet-0.16.7 → annet-0.16.9}/annet/annlib/netdev/views/hardware.py +31 -0
  8. annet-0.16.9/annet/bgp_models.py +266 -0
  9. {annet-0.16.7 → annet-0.16.9}/annet/configs/context.yml +2 -3
  10. {annet-0.16.7 → annet-0.16.9}/annet/configs/logging.yaml +1 -0
  11. {annet-0.16.7 → annet-0.16.9}/annet/generators/__init__.py +18 -4
  12. {annet-0.16.7 → annet-0.16.9}/annet/implicit.py +5 -0
  13. annet-0.16.9/annet/mesh/__init__.py +16 -0
  14. annet-0.16.9/annet/mesh/basemodel.py +180 -0
  15. annet-0.16.9/annet/mesh/device_models.py +62 -0
  16. annet-0.16.9/annet/mesh/executor.py +248 -0
  17. annet-0.16.9/annet/mesh/match_args.py +165 -0
  18. annet-0.16.9/annet/mesh/models_converter.py +84 -0
  19. annet-0.16.9/annet/mesh/peer_models.py +98 -0
  20. annet-0.16.9/annet/mesh/registry.py +212 -0
  21. annet-0.16.9/annet/rulebook/nexus/__init__.py +0 -0
  22. annet-0.16.9/annet/rulebook/routeros/__init__.py +0 -0
  23. annet-0.16.9/annet/rulebook/routeros/file.py +5 -0
  24. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/cisco.order +5 -2
  25. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/cisco.rul +6 -0
  26. annet-0.16.9/annet/rulebook/texts/pc.deploy +0 -0
  27. {annet-0.16.7 → annet-0.16.9}/annet/storage.py +41 -2
  28. {annet-0.16.7 → annet-0.16.9/annet.egg-info}/PKG-INFO +3 -1
  29. {annet-0.16.7 → annet-0.16.9}/annet.egg-info/SOURCES.txt +20 -1
  30. {annet-0.16.7 → annet-0.16.9}/annet.egg-info/requires.txt +4 -0
  31. annet-0.16.9/annet_generators/__init__.py +0 -0
  32. {annet-0.16.7 → annet-0.16.9}/annet_generators/example/__init__.py +1 -3
  33. annet-0.16.9/annet_generators/mesh_example/__init__.py +9 -0
  34. annet-0.16.9/annet_generators/mesh_example/bgp.py +43 -0
  35. annet-0.16.9/annet_generators/mesh_example/mesh_logic.py +28 -0
  36. {annet-0.16.7 → annet-0.16.9}/requirements.txt +2 -0
  37. annet-0.16.7/annet/adapters/netbox/common/models.py +0 -171
  38. {annet-0.16.7 → annet-0.16.9}/AUTHORS +0 -0
  39. {annet-0.16.7 → annet-0.16.9}/LICENSE +0 -0
  40. {annet-0.16.7 → annet-0.16.9}/MANIFEST.in +0 -0
  41. {annet-0.16.7 → annet-0.16.9}/annet/__init__.py +0 -0
  42. {annet-0.16.7 → annet-0.16.9}/annet/adapters/__init__.py +0 -0
  43. {annet-0.16.7/annet/adapters/netbox → annet-0.16.9/annet/adapters/fetchers}/__init__.py +0 -0
  44. {annet-0.16.7/annet/adapters/netbox/common → annet-0.16.9/annet/adapters/fetchers/stub}/__init__.py +0 -0
  45. {annet-0.16.7/annet/adapters/netbox/v24 → annet-0.16.9/annet/adapters/file}/__init__.py +0 -0
  46. {annet-0.16.7/annet/adapters/netbox/v37 → annet-0.16.9/annet/adapters/netbox}/__init__.py +0 -0
  47. {annet-0.16.7/annet/annlib/netdev → annet-0.16.9/annet/adapters/netbox/common}/__init__.py +0 -0
  48. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/common/client.py +0 -0
  49. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/common/manufacturer.py +0 -0
  50. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/common/query.py +0 -0
  51. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/common/status_client.py +0 -0
  52. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/common/storage_opts.py +0 -0
  53. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/provider.py +0 -0
  54. {annet-0.16.7/annet/annlib/netdev/views → annet-0.16.9/annet/adapters/netbox/v24}/__init__.py +0 -0
  55. {annet-0.16.7 → annet-0.16.9}/annet/adapters/netbox/v24/storage.py +0 -0
  56. {annet-0.16.7/annet/annlib/rbparser → annet-0.16.9/annet/adapters/netbox/v37}/__init__.py +0 -0
  57. {annet-0.16.7 → annet-0.16.9}/annet/annet.py +0 -0
  58. {annet-0.16.7 → annet-0.16.9}/annet/annlib/__init__.py +0 -0
  59. {annet-0.16.7 → annet-0.16.9}/annet/annlib/command.py +0 -0
  60. {annet-0.16.7 → annet-0.16.9}/annet/annlib/diff.py +0 -0
  61. {annet-0.16.7 → annet-0.16.9}/annet/annlib/errors.py +0 -0
  62. {annet-0.16.7 → annet-0.16.9}/annet/annlib/filter_acl.py +0 -0
  63. {annet-0.16.7 → annet-0.16.9}/annet/annlib/jsontools.py +0 -0
  64. {annet-0.16.7 → annet-0.16.9}/annet/annlib/lib.py +0 -0
  65. {annet-0.16.7/annet/annlib/rulebook → annet-0.16.9/annet/annlib/netdev}/__init__.py +0 -0
  66. {annet-0.16.7 → annet-0.16.9}/annet/annlib/netdev/db.py +0 -0
  67. {annet-0.16.7 → annet-0.16.9}/annet/annlib/netdev/devdb/__init__.py +0 -0
  68. {annet-0.16.7 → annet-0.16.9}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  69. {annet-0.16.7/annet/generators/common → annet-0.16.9/annet/annlib/netdev/views}/__init__.py +0 -0
  70. {annet-0.16.7 → annet-0.16.9}/annet/annlib/netdev/views/dump.py +0 -0
  71. {annet-0.16.7 → annet-0.16.9}/annet/annlib/output.py +0 -0
  72. {annet-0.16.7 → annet-0.16.9}/annet/annlib/patching.py +0 -0
  73. {annet-0.16.7/annet/rulebook/arista → annet-0.16.9/annet/annlib/rbparser}/__init__.py +0 -0
  74. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rbparser/acl.py +0 -0
  75. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rbparser/deploying.py +0 -0
  76. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rbparser/ordering.py +0 -0
  77. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rbparser/platform.py +0 -0
  78. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rbparser/syntax.py +0 -0
  79. {annet-0.16.7/annet/rulebook/b4com → annet-0.16.9/annet/annlib/rulebook}/__init__.py +0 -0
  80. {annet-0.16.7 → annet-0.16.9}/annet/annlib/rulebook/common.py +0 -0
  81. {annet-0.16.7 → annet-0.16.9}/annet/annlib/tabparser.py +0 -0
  82. {annet-0.16.7 → annet-0.16.9}/annet/annlib/types.py +0 -0
  83. {annet-0.16.7 → annet-0.16.9}/annet/api/__init__.py +0 -0
  84. {annet-0.16.7 → annet-0.16.9}/annet/argparse.py +0 -0
  85. {annet-0.16.7 → annet-0.16.9}/annet/cli.py +0 -0
  86. {annet-0.16.7 → annet-0.16.9}/annet/cli_args.py +0 -0
  87. {annet-0.16.7 → annet-0.16.9}/annet/connectors.py +0 -0
  88. {annet-0.16.7 → annet-0.16.9}/annet/deploy.py +0 -0
  89. {annet-0.16.7 → annet-0.16.9}/annet/diff.py +0 -0
  90. {annet-0.16.7 → annet-0.16.9}/annet/executor.py +0 -0
  91. {annet-0.16.7 → annet-0.16.9}/annet/filtering.py +0 -0
  92. {annet-0.16.7 → annet-0.16.9}/annet/gen.py +0 -0
  93. {annet-0.16.7 → annet-0.16.9}/annet/generators/base.py +0 -0
  94. {annet-0.16.7/annet/rulebook/cisco → annet-0.16.9/annet/generators/common}/__init__.py +0 -0
  95. {annet-0.16.7 → annet-0.16.9}/annet/generators/common/initial.py +0 -0
  96. {annet-0.16.7 → annet-0.16.9}/annet/generators/entire.py +0 -0
  97. {annet-0.16.7 → annet-0.16.9}/annet/generators/exceptions.py +0 -0
  98. {annet-0.16.7 → annet-0.16.9}/annet/generators/jsonfragment.py +0 -0
  99. {annet-0.16.7 → annet-0.16.9}/annet/generators/partial.py +0 -0
  100. {annet-0.16.7 → annet-0.16.9}/annet/generators/perf.py +0 -0
  101. {annet-0.16.7 → annet-0.16.9}/annet/generators/ref.py +0 -0
  102. {annet-0.16.7 → annet-0.16.9}/annet/generators/result.py +0 -0
  103. {annet-0.16.7 → annet-0.16.9}/annet/hardware.py +0 -0
  104. {annet-0.16.7 → annet-0.16.9}/annet/lib.py +0 -0
  105. {annet-0.16.7 → annet-0.16.9}/annet/output.py +0 -0
  106. {annet-0.16.7 → annet-0.16.9}/annet/parallel.py +0 -0
  107. {annet-0.16.7 → annet-0.16.9}/annet/patching.py +0 -0
  108. {annet-0.16.7 → annet-0.16.9}/annet/reference.py +0 -0
  109. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/__init__.py +0 -0
  110. {annet-0.16.7/annet/rulebook/huawei → annet-0.16.9/annet/rulebook/arista}/__init__.py +0 -0
  111. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/arista/iface.py +0 -0
  112. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/aruba/__init__.py +0 -0
  113. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/aruba/ap_env.py +0 -0
  114. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/aruba/misc.py +0 -0
  115. {annet-0.16.7/annet/rulebook/nexus → annet-0.16.9/annet/rulebook/b4com}/__init__.py +0 -0
  116. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/b4com/file.py +0 -0
  117. {annet-0.16.7/annet_generators → annet-0.16.9/annet/rulebook/cisco}/__init__.py +0 -0
  118. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/cisco/iface.py +0 -0
  119. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/cisco/misc.py +0 -0
  120. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/cisco/vlandb.py +0 -0
  121. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/common.py +0 -0
  122. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/deploying.py +0 -0
  123. /annet-0.16.7/annet/rulebook/texts/pc.deploy → /annet-0.16.9/annet/rulebook/huawei/__init__.py +0 -0
  124. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/huawei/aaa.py +0 -0
  125. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/huawei/bgp.py +0 -0
  126. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/huawei/iface.py +0 -0
  127. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/huawei/misc.py +0 -0
  128. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/huawei/vlandb.py +0 -0
  129. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/juniper/__init__.py +0 -0
  130. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/nexus/iface.py +0 -0
  131. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/patching.py +0 -0
  132. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/ribbon/__init__.py +0 -0
  133. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/arista.deploy +0 -0
  134. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/arista.order +0 -0
  135. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/arista.rul +0 -0
  136. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/aruba.deploy +0 -0
  137. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/aruba.order +0 -0
  138. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/aruba.rul +0 -0
  139. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/b4com.deploy +0 -0
  140. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/b4com.order +0 -0
  141. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/b4com.rul +0 -0
  142. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/cisco.deploy +0 -0
  143. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/huawei.deploy +0 -0
  144. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/huawei.order +0 -0
  145. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/huawei.rul +0 -0
  146. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/juniper.rul +0 -0
  147. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/nexus.deploy +0 -0
  148. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/nexus.order +0 -0
  149. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/nexus.rul +0 -0
  150. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/nokia.rul +0 -0
  151. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/optixtrans.deploy +0 -0
  152. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/optixtrans.order +0 -0
  153. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/optixtrans.rul +0 -0
  154. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/pc.order +0 -0
  155. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/pc.rul +0 -0
  156. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/ribbon.deploy +0 -0
  157. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/ribbon.rul +0 -0
  158. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/routeros.order +0 -0
  159. {annet-0.16.7 → annet-0.16.9}/annet/rulebook/texts/routeros.rul +0 -0
  160. {annet-0.16.7 → annet-0.16.9}/annet/tabparser.py +0 -0
  161. {annet-0.16.7 → annet-0.16.9}/annet/text_term_format.py +0 -0
  162. {annet-0.16.7 → annet-0.16.9}/annet/tracing.py +0 -0
  163. {annet-0.16.7 → annet-0.16.9}/annet/types.py +0 -0
  164. {annet-0.16.7 → annet-0.16.9}/annet.egg-info/dependency_links.txt +0 -0
  165. {annet-0.16.7 → annet-0.16.9}/annet.egg-info/entry_points.txt +0 -0
  166. {annet-0.16.7 → annet-0.16.9}/annet.egg-info/top_level.txt +0 -0
  167. {annet-0.16.7 → annet-0.16.9}/annet_generators/example/lldp.py +0 -0
  168. {annet-0.16.7 → annet-0.16.9}/setup.cfg +0 -0
  169. {annet-0.16.7 → annet-0.16.9}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.16.7
3
+ Version: 0.16.9
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
+ Requires-Dist: requests>=2.32.3
27
+ Requires-Dist: annetbox>=0.1.8; python_version > "3.9"
@@ -1,13 +1,15 @@
1
- # Annet - network configuration utility
1
+ # Annet - configuration generation and deploying utility for network equipment
2
2
 
3
- The system contains network appliance config generators written in Python with optional use of text preprocessors (Jinja2, Mako).
4
- Huawei, Cisco IOS, Cisco NX-OS, Cisco IOS-XR, Juniper, as well as devices configured via separate config files (Linux, FreeBSD, Cumulus) are supported.
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.
5
4
 
6
- annet has a number of modes (subcommands):
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):
7
8
 
8
9
  - ```annet gen``` - generates the entire config for the specified devices or specified parts of it
9
10
  - ```annet diff``` - first does gen and then builds diff with current config version
10
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
11
13
 
12
14
  Usage help can be obtained by calling ```annet -h``` or for a specific command, such as ```annet gen -h```.
13
15
 
@@ -68,7 +70,7 @@ So you need to write the undo logic for the ```rule ```` command in the ``acl ``
68
70
  Here is the part of rulebook/texts/huawei.rul responsible for this:
69
71
  ```
70
72
  acl name *
71
- rule * %logic=huawei.misc.undo_redo
73
+ rule * %logic=huawei.misc.undo_redo
72
74
  ```
73
75
  The asterisk here means that the key argument of the undo_redo function will contain the first word after rule, namely the rule number.
74
76
 
@@ -77,7 +79,7 @@ Here, the undo_redo function from the file in rulebook/huawei/misc.py is used to
77
79
  def undo_redo(rule, key, diff, **_):
78
80
  ...
79
81
  ```
80
- Now calling ```annet patch -g snmp sw1-i38```` returns the correct set of commands.
82
+ Now calling `annet patch -g snmp sw1-i38` returns the correct set of commands.
81
83
  ```
82
84
  acl number 2610
83
85
  undo rule 40
@@ -85,21 +87,62 @@ acl number 2610
85
87
  quit
86
88
  ```
87
89
 
88
-
89
- <!-- ### annet deploy
90
+ ### annet deploy
90
91
 
91
92
  To apply these commands on a switch there is a **deploy** module.
92
- annet can apply changes (roll out) to multiple devices at the same time.
93
+ Annet can apply changes (roll out) to multiple devices at the same time.
93
94
 
94
95
  By default, the edits that annet proposes to roll out will be shown before the rollout.
95
96
  The user must confirm that they agree to roll out the proposed diff to a given list of devices.
96
97
  During the rollout, annet will display the overall progress of the task and the log of one of the devices.
97
98
 
98
99
  Normal layout. The screen with patches will be shown and the process of laying out will be displayed.
100
+
99
101
  ```bash
100
102
  annet deploy -g snmp $HOST
101
103
  ```
102
- Credentials will be used from the current user (username, ssh key, ssh agent, encrypted password in $HOME). -->
104
+
105
+ Credentials will be used from the current user (username, ssh key, ssh agent).
106
+
107
+ ## Installation
108
+
109
+ ```shell
110
+ mkdir myproject
111
+ cd myproject
112
+ python3 -m venv venv
113
+ source venv/bin/activate
114
+ pip install annet gnetcli_adapter
115
+
116
+ cat > ~/.annet/context.yml_tmp<<EOF
117
+ fetcher:
118
+ default:
119
+ adapter: gnetcli
120
+ deployer:
121
+ default:
122
+ adapter: gnetcli
123
+ generators:
124
+ default:
125
+ - my_generators
126
+ storage:
127
+ netbox:
128
+ adapter: netbox
129
+ params:
130
+ url: http://127.0.0.1:8000
131
+ token: 1234567890abcdef01234567890abcdef0123456
132
+ context:
133
+ default:
134
+ fetcher: default
135
+ deployer: default
136
+ connection: default
137
+ generators: default
138
+ storage: default
139
+ selected_context: default
140
+ EOF
141
+
142
+ cp -r my_generators
143
+
144
+ annet deploy mydevice
145
+ ```
103
146
 
104
147
  ## Configuration
105
148
 
@@ -174,3 +217,20 @@ devices:
174
217
 
175
218
  Annet uses [Entry Points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) mechanism for customization.
176
219
  For example, you can implement the Storage interface on top of your favorite inventory system.
220
+
221
+
222
+ ## Building doc
223
+
224
+ 1. Install dependencies:
225
+
226
+ ```shell
227
+ pip install -r requirements-doc.txt
228
+ ```
229
+
230
+ 2. Build
231
+
232
+ ```shell
233
+ sphinx-build -M html docs docs-build
234
+ ```
235
+
236
+ 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})
@@ -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"
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