annet 0.16.33__tar.gz → 0.16.35__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 (193) hide show
  1. {annet-0.16.33/annet.egg-info → annet-0.16.35}/PKG-INFO +2 -2
  2. annet-0.16.35/annet/adapters/netbox/common/query.py +61 -0
  3. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/v37/storage.py +85 -23
  4. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/devdb/data/devdb.json +3 -1
  5. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rulebook/common.py +2 -2
  6. {annet-0.16.33 → annet-0.16.35}/annet/bgp_models.py +12 -16
  7. {annet-0.16.33 → annet-0.16.35}/annet/mesh/device_models.py +1 -2
  8. {annet-0.16.33 → annet-0.16.35}/annet/mesh/executor.py +9 -0
  9. {annet-0.16.33 → annet-0.16.35}/annet/mesh/registry.py +12 -12
  10. annet-0.16.35/annet/rulebook/b4com/iface.py +42 -0
  11. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/b4com.rul +9 -4
  12. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/huawei.deploy +1 -1
  13. {annet-0.16.33 → annet-0.16.35/annet.egg-info}/PKG-INFO +2 -2
  14. {annet-0.16.33 → annet-0.16.35}/annet.egg-info/SOURCES.txt +1 -0
  15. {annet-0.16.33 → annet-0.16.35}/annet.egg-info/requires.txt +1 -1
  16. {annet-0.16.33 → annet-0.16.35}/setup.py +1 -1
  17. annet-0.16.33/annet/adapters/netbox/common/query.py +0 -26
  18. {annet-0.16.33 → annet-0.16.35}/AUTHORS +0 -0
  19. {annet-0.16.33 → annet-0.16.35}/LICENSE +0 -0
  20. {annet-0.16.33 → annet-0.16.35}/MANIFEST.in +0 -0
  21. {annet-0.16.33 → annet-0.16.35}/README.md +0 -0
  22. {annet-0.16.33 → annet-0.16.35}/annet/__init__.py +0 -0
  23. {annet-0.16.33 → annet-0.16.35}/annet/adapters/__init__.py +0 -0
  24. {annet-0.16.33 → annet-0.16.35}/annet/adapters/fetchers/__init__.py +0 -0
  25. {annet-0.16.33 → annet-0.16.35}/annet/adapters/fetchers/stub/__init__.py +0 -0
  26. {annet-0.16.33 → annet-0.16.35}/annet/adapters/fetchers/stub/fetcher.py +0 -0
  27. {annet-0.16.33 → annet-0.16.35}/annet/adapters/file/__init__.py +0 -0
  28. {annet-0.16.33 → annet-0.16.35}/annet/adapters/file/provider.py +0 -0
  29. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/__init__.py +0 -0
  30. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/__init__.py +0 -0
  31. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/client.py +0 -0
  32. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/manufacturer.py +0 -0
  33. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/models.py +0 -0
  34. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/status_client.py +0 -0
  35. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/common/storage_opts.py +0 -0
  36. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/provider.py +0 -0
  37. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/v24/__init__.py +0 -0
  38. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/v24/storage.py +0 -0
  39. {annet-0.16.33 → annet-0.16.35}/annet/adapters/netbox/v37/__init__.py +0 -0
  40. {annet-0.16.33 → annet-0.16.35}/annet/annet.py +0 -0
  41. {annet-0.16.33 → annet-0.16.35}/annet/annlib/__init__.py +0 -0
  42. {annet-0.16.33 → annet-0.16.35}/annet/annlib/command.py +0 -0
  43. {annet-0.16.33 → annet-0.16.35}/annet/annlib/diff.py +0 -0
  44. {annet-0.16.33 → annet-0.16.35}/annet/annlib/errors.py +0 -0
  45. {annet-0.16.33 → annet-0.16.35}/annet/annlib/filter_acl.py +0 -0
  46. {annet-0.16.33 → annet-0.16.35}/annet/annlib/jsontools.py +0 -0
  47. {annet-0.16.33 → annet-0.16.35}/annet/annlib/lib.py +0 -0
  48. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/__init__.py +0 -0
  49. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/db.py +0 -0
  50. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/devdb/__init__.py +0 -0
  51. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/views/__init__.py +0 -0
  52. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/views/dump.py +0 -0
  53. {annet-0.16.33 → annet-0.16.35}/annet/annlib/netdev/views/hardware.py +0 -0
  54. {annet-0.16.33 → annet-0.16.35}/annet/annlib/output.py +0 -0
  55. {annet-0.16.33 → annet-0.16.35}/annet/annlib/patching.py +0 -0
  56. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/__init__.py +0 -0
  57. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/acl.py +0 -0
  58. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/deploying.py +0 -0
  59. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/ordering.py +0 -0
  60. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/platform.py +0 -0
  61. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rbparser/syntax.py +0 -0
  62. {annet-0.16.33 → annet-0.16.35}/annet/annlib/rulebook/__init__.py +0 -0
  63. {annet-0.16.33 → annet-0.16.35}/annet/annlib/tabparser.py +0 -0
  64. {annet-0.16.33 → annet-0.16.35}/annet/annlib/types.py +0 -0
  65. {annet-0.16.33 → annet-0.16.35}/annet/api/__init__.py +0 -0
  66. {annet-0.16.33 → annet-0.16.35}/annet/argparse.py +0 -0
  67. {annet-0.16.33 → annet-0.16.35}/annet/cli.py +0 -0
  68. {annet-0.16.33 → annet-0.16.35}/annet/cli_args.py +0 -0
  69. {annet-0.16.33 → annet-0.16.35}/annet/configs/context.yml +0 -0
  70. {annet-0.16.33 → annet-0.16.35}/annet/configs/logging.yaml +0 -0
  71. {annet-0.16.33 → annet-0.16.35}/annet/connectors.py +0 -0
  72. {annet-0.16.33 → annet-0.16.35}/annet/deploy.py +0 -0
  73. {annet-0.16.33 → annet-0.16.35}/annet/diff.py +0 -0
  74. {annet-0.16.33 → annet-0.16.35}/annet/executor.py +0 -0
  75. {annet-0.16.33 → annet-0.16.35}/annet/filtering.py +0 -0
  76. {annet-0.16.33 → annet-0.16.35}/annet/gen.py +0 -0
  77. {annet-0.16.33 → annet-0.16.35}/annet/generators/__init__.py +0 -0
  78. {annet-0.16.33 → annet-0.16.35}/annet/generators/base.py +0 -0
  79. {annet-0.16.33 → annet-0.16.35}/annet/generators/common/__init__.py +0 -0
  80. {annet-0.16.33 → annet-0.16.35}/annet/generators/common/initial.py +0 -0
  81. {annet-0.16.33 → annet-0.16.35}/annet/generators/entire.py +0 -0
  82. {annet-0.16.33 → annet-0.16.35}/annet/generators/exceptions.py +0 -0
  83. {annet-0.16.33 → annet-0.16.35}/annet/generators/jsonfragment.py +0 -0
  84. {annet-0.16.33 → annet-0.16.35}/annet/generators/partial.py +0 -0
  85. {annet-0.16.33 → annet-0.16.35}/annet/generators/perf.py +0 -0
  86. {annet-0.16.33 → annet-0.16.35}/annet/generators/ref.py +0 -0
  87. {annet-0.16.33 → annet-0.16.35}/annet/generators/result.py +0 -0
  88. {annet-0.16.33 → annet-0.16.35}/annet/hardware.py +0 -0
  89. {annet-0.16.33 → annet-0.16.35}/annet/implicit.py +0 -0
  90. {annet-0.16.33 → annet-0.16.35}/annet/lib.py +0 -0
  91. {annet-0.16.33 → annet-0.16.35}/annet/mesh/__init__.py +0 -0
  92. {annet-0.16.33 → annet-0.16.35}/annet/mesh/basemodel.py +0 -0
  93. {annet-0.16.33 → annet-0.16.35}/annet/mesh/match_args.py +0 -0
  94. {annet-0.16.33 → annet-0.16.35}/annet/mesh/models_converter.py +0 -0
  95. {annet-0.16.33 → annet-0.16.35}/annet/mesh/peer_models.py +0 -0
  96. {annet-0.16.33 → annet-0.16.35}/annet/mesh/port_processor.py +0 -0
  97. {annet-0.16.33 → annet-0.16.35}/annet/output.py +0 -0
  98. {annet-0.16.33 → annet-0.16.35}/annet/parallel.py +0 -0
  99. {annet-0.16.33 → annet-0.16.35}/annet/patching.py +0 -0
  100. {annet-0.16.33 → annet-0.16.35}/annet/reference.py +0 -0
  101. {annet-0.16.33 → annet-0.16.35}/annet/rpl/__init__.py +0 -0
  102. {annet-0.16.33 → annet-0.16.35}/annet/rpl/action.py +0 -0
  103. {annet-0.16.33 → annet-0.16.35}/annet/rpl/condition.py +0 -0
  104. {annet-0.16.33 → annet-0.16.35}/annet/rpl/match_builder.py +0 -0
  105. {annet-0.16.33 → annet-0.16.35}/annet/rpl/policy.py +0 -0
  106. {annet-0.16.33 → annet-0.16.35}/annet/rpl/result.py +0 -0
  107. {annet-0.16.33 → annet-0.16.35}/annet/rpl/routemap.py +0 -0
  108. {annet-0.16.33 → annet-0.16.35}/annet/rpl/statement_builder.py +0 -0
  109. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/__init__.py +0 -0
  110. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/aspath.py +0 -0
  111. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/community.py +0 -0
  112. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/cumulus_frr.py +0 -0
  113. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/entities.py +0 -0
  114. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/execute.py +0 -0
  115. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/policy.py +0 -0
  116. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/prefix_lists.py +0 -0
  117. {annet-0.16.33 → annet-0.16.35}/annet/rpl_generators/rd.py +0 -0
  118. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/__init__.py +0 -0
  119. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/arista/__init__.py +0 -0
  120. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/arista/iface.py +0 -0
  121. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/aruba/__init__.py +0 -0
  122. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/aruba/ap_env.py +0 -0
  123. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/aruba/misc.py +0 -0
  124. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/b4com/__init__.py +0 -0
  125. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/b4com/file.py +0 -0
  126. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/cisco/__init__.py +0 -0
  127. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/cisco/iface.py +0 -0
  128. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/cisco/misc.py +0 -0
  129. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/cisco/vlandb.py +0 -0
  130. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/common.py +0 -0
  131. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/deploying.py +0 -0
  132. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/__init__.py +0 -0
  133. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/aaa.py +0 -0
  134. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/bgp.py +0 -0
  135. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/iface.py +0 -0
  136. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/misc.py +0 -0
  137. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/huawei/vlandb.py +0 -0
  138. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/juniper/__init__.py +0 -0
  139. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/nexus/__init__.py +0 -0
  140. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/nexus/iface.py +0 -0
  141. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/patching.py +0 -0
  142. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/ribbon/__init__.py +0 -0
  143. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/routeros/__init__.py +0 -0
  144. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/routeros/file.py +0 -0
  145. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/arista.deploy +0 -0
  146. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/arista.order +0 -0
  147. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/arista.rul +0 -0
  148. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/aruba.deploy +0 -0
  149. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/aruba.order +0 -0
  150. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/aruba.rul +0 -0
  151. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/b4com.deploy +0 -0
  152. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/b4com.order +0 -0
  153. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/cisco.deploy +0 -0
  154. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/cisco.order +0 -0
  155. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/cisco.rul +0 -0
  156. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/huawei.order +0 -0
  157. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/huawei.rul +0 -0
  158. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/juniper.rul +0 -0
  159. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/nexus.deploy +0 -0
  160. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/nexus.order +0 -0
  161. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/nexus.rul +0 -0
  162. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/nokia.rul +0 -0
  163. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/optixtrans.deploy +0 -0
  164. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/optixtrans.order +0 -0
  165. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/optixtrans.rul +0 -0
  166. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/pc.deploy +0 -0
  167. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/pc.order +0 -0
  168. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/pc.rul +0 -0
  169. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/ribbon.deploy +0 -0
  170. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/ribbon.rul +0 -0
  171. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/routeros.order +0 -0
  172. {annet-0.16.33 → annet-0.16.35}/annet/rulebook/texts/routeros.rul +0 -0
  173. {annet-0.16.33 → annet-0.16.35}/annet/storage.py +0 -0
  174. {annet-0.16.33 → annet-0.16.35}/annet/tabparser.py +0 -0
  175. {annet-0.16.33 → annet-0.16.35}/annet/text_term_format.py +0 -0
  176. {annet-0.16.33 → annet-0.16.35}/annet/tracing.py +0 -0
  177. {annet-0.16.33 → annet-0.16.35}/annet/types.py +0 -0
  178. {annet-0.16.33 → annet-0.16.35}/annet.egg-info/dependency_links.txt +0 -0
  179. {annet-0.16.33 → annet-0.16.35}/annet.egg-info/entry_points.txt +0 -0
  180. {annet-0.16.33 → annet-0.16.35}/annet.egg-info/top_level.txt +0 -0
  181. {annet-0.16.33 → annet-0.16.35}/annet_generators/__init__.py +0 -0
  182. {annet-0.16.33 → annet-0.16.35}/annet_generators/example/__init__.py +0 -0
  183. {annet-0.16.33 → annet-0.16.35}/annet_generators/example/lldp.py +0 -0
  184. {annet-0.16.33 → annet-0.16.35}/annet_generators/mesh_example/__init__.py +0 -0
  185. {annet-0.16.33 → annet-0.16.35}/annet_generators/mesh_example/bgp.py +0 -0
  186. {annet-0.16.33 → annet-0.16.35}/annet_generators/mesh_example/mesh_logic.py +0 -0
  187. {annet-0.16.33 → annet-0.16.35}/annet_generators/rpl_example/__init__.py +0 -0
  188. {annet-0.16.33 → annet-0.16.35}/annet_generators/rpl_example/generator.py +0 -0
  189. {annet-0.16.33 → annet-0.16.35}/annet_generators/rpl_example/items.py +0 -0
  190. {annet-0.16.33 → annet-0.16.35}/annet_generators/rpl_example/mesh.py +0 -0
  191. {annet-0.16.33 → annet-0.16.35}/annet_generators/rpl_example/route_policy.py +0 -0
  192. {annet-0.16.33 → annet-0.16.35}/requirements.txt +0 -0
  193. {annet-0.16.33 → annet-0.16.35}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 0.16.33
3
+ Version: 0.16.35
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -23,7 +23,7 @@ Requires-Dist: yarl>=1.8.2
23
23
  Requires-Dist: adaptix==3.0.0b7
24
24
  Requires-Dist: dataclass-rest==0.4
25
25
  Provides-Extra: netbox
26
- Requires-Dist: annetbox[sync]>=0.1.10; extra == "netbox"
26
+ Requires-Dist: annetbox[sync]>=0.1.12; extra == "netbox"
27
27
  Dynamic: home-page
28
28
  Dynamic: license
29
29
  Dynamic: provides-extra
@@ -0,0 +1,61 @@
1
+ from collections import defaultdict
2
+ from dataclasses import dataclass
3
+ from typing import cast, List, Union, Iterable, Optional, TypedDict
4
+
5
+ from annet.storage import Query
6
+
7
+ FIELD_VALUE_SEPARATOR = ":"
8
+ ALLOWED_GLOB_GROUPS = ["site", "tag", "role"]
9
+
10
+
11
+ class Filter(TypedDict, total=False):
12
+ site: list[str]
13
+ tag: list[str]
14
+ role: list[str]
15
+ name: list[str]
16
+
17
+
18
+ @dataclass
19
+ class NetboxQuery(Query):
20
+ query: List[str]
21
+
22
+ @classmethod
23
+ def new(
24
+ cls, query: Union[str, Iterable[str]],
25
+ hosts_range: Optional[slice] = None,
26
+ ) -> "NetboxQuery":
27
+ if hosts_range is not None:
28
+ raise ValueError("host_range is not supported")
29
+ return cls(query=list(query))
30
+
31
+ @property
32
+ def globs(self):
33
+ # We process every query host as a glob
34
+ return self.query
35
+
36
+ def parse_query(self) -> Filter:
37
+ query_groups = defaultdict(list)
38
+ for q in self.globs:
39
+ if FIELD_VALUE_SEPARATOR in q:
40
+ glob_type, param = q.split(FIELD_VALUE_SEPARATOR, 2)
41
+ if glob_type not in ALLOWED_GLOB_GROUPS:
42
+ raise Exception(f"unknown query type: '{glob_type}'")
43
+ if not param:
44
+ raise Exception(f"empty param for '{glob_type}'")
45
+ query_groups[glob_type].append(param)
46
+ else:
47
+ query_groups["name"].append(q)
48
+
49
+ query_groups.default_factory = None
50
+ return cast(Filter, query_groups)
51
+
52
+ def is_empty(self) -> bool:
53
+ return len(self.query) == 0
54
+
55
+ def is_host_query(self) -> bool:
56
+ if not self.globs:
57
+ return False
58
+ for q in self.globs:
59
+ if FIELD_VALUE_SEPARATOR in q:
60
+ return False
61
+ return True
@@ -1,8 +1,8 @@
1
- from logging import getLogger
2
- from typing import Any, Optional, List, Union, Dict
3
- from ipaddress import ip_interface
4
- from collections import defaultdict
5
1
  import ssl
2
+ from collections import defaultdict
3
+ from ipaddress import ip_interface
4
+ from logging import getLogger
5
+ from typing import Any, Optional, List, Union, Dict, cast
6
6
 
7
7
  from adaptix import P
8
8
  from adaptix.conversion import impl_converter, link, link_constant
@@ -13,7 +13,7 @@ from annet.adapters.netbox.common import models
13
13
  from annet.adapters.netbox.common.manufacturer import (
14
14
  get_hw, get_breed,
15
15
  )
16
- from annet.adapters.netbox.common.query import NetboxQuery
16
+ from annet.adapters.netbox.common.query import NetboxQuery, FIELD_VALUE_SEPARATOR
17
17
  from annet.adapters.netbox.common.storage_opts import NetboxStorageOpts
18
18
  from annet.annlib.netdev.views.hardware import HardwareView
19
19
  from annet.storage import Storage, Device, Interface
@@ -101,6 +101,9 @@ class NetboxStorageV37(Storage):
101
101
  self.exact_host_filter = opts.exact_host_filter
102
102
  self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx)
103
103
  self._all_fqdns: Optional[list[str]] = None
104
+ self._id_devices: dict[int, models.NetboxDevice] = {}
105
+ self._name_devices: dict[str, models.NetboxDevice] = {}
106
+ self._short_name_devices: dict[str, models.NetboxDevice] = {}
104
107
 
105
108
  def __enter__(self):
106
109
  return self
@@ -136,6 +139,37 @@ class NetboxStorageV37(Storage):
136
139
  ) -> List[models.NetboxDevice]:
137
140
  if isinstance(query, list):
138
141
  query = NetboxQuery.new(query)
142
+
143
+ devices = []
144
+ if query.is_host_query():
145
+ globs = []
146
+ for glob in query.globs:
147
+ if glob in self._name_devices:
148
+ devices.append(self._name_devices[glob])
149
+ if glob in self._short_name_devices:
150
+ devices.append(self._short_name_devices[glob])
151
+ else:
152
+ globs.append(glob)
153
+ if not globs:
154
+ return devices
155
+ query = NetboxQuery.new(globs)
156
+
157
+ return devices + self._make_devices(
158
+ query=query,
159
+ preload_neighbors=preload_neighbors,
160
+ use_mesh=use_mesh,
161
+ preload_extra_fields=preload_extra_fields,
162
+ **kwargs
163
+ )
164
+
165
+ def _make_devices(
166
+ self,
167
+ query: NetboxQuery,
168
+ preload_neighbors=False,
169
+ use_mesh=None,
170
+ preload_extra_fields=False,
171
+ **kwargs,
172
+ ) -> List[models.NetboxDevice]:
139
173
  device_ids = {
140
174
  device.id: extend_device(
141
175
  device=device,
@@ -148,6 +182,9 @@ class NetboxStorageV37(Storage):
148
182
  if not device_ids:
149
183
  return []
150
184
 
185
+ for device in device_ids.values():
186
+ self._record_device(device)
187
+
151
188
  interfaces = self._load_interfaces(list(device_ids))
152
189
  neighbours = {x.id: x for x in self._load_neighbours(interfaces)}
153
190
  neighbours_seen: dict[str, set] = defaultdict(set)
@@ -162,21 +199,22 @@ class NetboxStorageV37(Storage):
162
199
 
163
200
  return list(device_ids.values())
164
201
 
202
+ def _record_device(self, device: models.NetboxDevice):
203
+ self._id_devices[device.id] = device
204
+ self._short_name_devices[device.name] = device
205
+ if not self.exact_host_filter:
206
+ short_name = device.name.split(".")[0]
207
+ self._short_name_devices[short_name] = device
208
+
165
209
  def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
166
210
  if not query.globs:
167
211
  return []
168
- if self.exact_host_filter:
169
- devices = self.netbox.dcim_all_devices(name__ie=query.globs).results
170
- else:
171
- query = _hostname_dot_hack(query)
172
- devices = [
173
- device
174
- for device in self.netbox.dcim_all_devices(
175
- name__ic=query.globs,
176
- ).results
177
- if _match_query(query, device)
178
- ]
179
- return devices
212
+ query_groups = parse_glob(self.exact_host_filter, query)
213
+ return [
214
+ device
215
+ for device in self.netbox.dcim_all_devices(**query_groups).results
216
+ if _match_query(self.exact_host_filter, query, device)
217
+ ]
180
218
 
181
219
  def _extend_interfaces(self, interfaces: List[models.Interface]) -> List[models.Interface]:
182
220
  extended_ifaces = {
@@ -227,6 +265,9 @@ class NetboxStorageV37(Storage):
227
265
  self, obj_id, preload_neighbors=False, use_mesh=None,
228
266
  **kwargs,
229
267
  ) -> models.NetboxDevice:
268
+ if obj_id in self._id_devices:
269
+ return self._id_devices[obj_id]
270
+
230
271
  device = self.netbox.dcim_device(obj_id)
231
272
  interfaces = self._load_interfaces([device.id])
232
273
  neighbours = self._load_neighbours(interfaces)
@@ -237,6 +278,7 @@ class NetboxStorageV37(Storage):
237
278
  interfaces=interfaces,
238
279
  neighbours=neighbours,
239
280
  )
281
+ self._record_device(res)
240
282
  return res
241
283
 
242
284
  def flush_perf(self):
@@ -261,14 +303,23 @@ class NetboxStorageV37(Storage):
261
303
  return res
262
304
 
263
305
 
264
- def _match_query(query: NetboxQuery, device_data: api_models.Device) -> bool:
265
- for subquery in query.globs:
266
- if subquery.strip() in device_data.name:
306
+ def _match_query(exact_host_filter: bool, query: NetboxQuery, device_data: api_models.Device) -> bool:
307
+ """
308
+ Additional filtering after netbox due to limited backend logic.
309
+ """
310
+ if exact_host_filter:
311
+ return True # nothing to check, all filtering is done by netbox
312
+ hostnames = [subquery.strip() for subquery in query.globs if FIELD_VALUE_SEPARATOR not in subquery]
313
+ if not hostnames:
314
+ return True # no hostnames to check
315
+ short_name = device_data.name.split(".")[0]
316
+ for hostname in hostnames:
317
+ if short_name == hostname or device_data.name == hostname:
267
318
  return True
268
319
  return False
269
320
 
270
321
 
271
- def _hostname_dot_hack(netbox_query: NetboxQuery) -> NetboxQuery:
322
+ def _hostname_dot_hack(raw_query: str) -> str:
272
323
  # there is no proper way to lookup host by its hostname
273
324
  # ie find "host" with fqdn "host.example.com"
274
325
  # besides using name__ic (ie startswith)
@@ -280,9 +331,20 @@ def _hostname_dot_hack(netbox_query: NetboxQuery) -> NetboxQuery:
280
331
  raw_query = raw_query + "."
281
332
  return raw_query
282
333
 
283
- raw_query = netbox_query.query
284
334
  if isinstance(raw_query, list):
285
335
  for i, name in enumerate(raw_query):
286
336
  raw_query[i] = add_dot(name)
337
+ elif isinstance(raw_query, str):
338
+ raw_query = add_dot(raw_query)
287
339
 
288
- return NetboxQuery(raw_query)
340
+ return raw_query
341
+
342
+
343
+ def parse_glob(exact_host_filter: bool, query: NetboxQuery) -> dict[str, list[str]]:
344
+ query_groups = cast(dict[str, list[str]], query.parse_query())
345
+ if names := query_groups.pop("name", None):
346
+ if exact_host_filter:
347
+ query_groups["name__ie"] = names
348
+ else:
349
+ query_groups["name__ic"] = [_hostname_dot_hack(name) for name in names]
350
+ return query_groups
@@ -166,6 +166,8 @@
166
166
 
167
167
  "B4com": "^[Bb]4com",
168
168
  "B4com.CS2148P": "^[Bb]4com B4T-CS2148P.*",
169
+ "B4com.CS4100": "^[Bb]4com B4T-CS41.*",
170
+ "B4com.CS4132U": "^[Bb]4com B4T-CS4132U.*",
169
171
  "B4com.CS4148Q": "^[Bb]4com B4T-CS4148Q.*",
170
- "B4com.CS4146U": "^[Bb]4com B4T-CS4146U.*"
172
+ "B4com.CS4164U": "^[Bb]4com B4T-CS4164U.*"
171
173
  }
@@ -346,14 +346,14 @@ def apply(hw, do_commit, do_finalize, **_):
346
346
  after.add_cmd(Command("exit"))
347
347
  elif hw.B4com.CS2148P:
348
348
  before.add_cmd(Command("conf t"))
349
- after.add_cmd(Command("exit"))
349
+ after.add_cmd(Command("end"))
350
350
  if do_finalize:
351
351
  after.add_cmd(Command("write", timeout=40))
352
352
  elif hw.B4com:
353
353
  before.add_cmd(Command("conf t"))
354
- after.add_cmd(Command("exit"))
355
354
  if do_commit:
356
355
  after.add_cmd(Command("commit"))
356
+ after.add_cmd(Command("end"))
357
357
  if do_finalize:
358
358
  after.add_cmd(Command("write", timeout=40))
359
359
  elif hw.H3C:
@@ -142,7 +142,6 @@ class Peer:
142
142
  class Aggregate:
143
143
  policy: str = ""
144
144
  routes: tuple[str, ...] = () # "182.168.1.0/24",
145
- export_policy: str = ""
146
145
  as_path: str = ""
147
146
  reference: str = ""
148
147
  suppress: bool = False
@@ -290,21 +289,18 @@ def _used_policies(peer: Union[Peer, PeerGroup]) -> Iterable[str]:
290
289
 
291
290
 
292
291
  def _used_redistribute_policies(opts: Union[GlobalOptions, VrfOptions]) -> Iterable[str]:
293
- for red in opts.ipv4_unicast.redistributes:
294
- if red.policy:
295
- yield red.policy
296
- for red in opts.ipv6_unicast.redistributes:
297
- if red.policy:
298
- yield red.policy
299
- for red in opts.ipv4_labeled_unicast.redistributes:
300
- if red.policy:
301
- yield red.policy
302
- for red in opts.ipv6_labeled_unicast.redistributes:
303
- if red.policy:
304
- yield red.policy
305
- for red in opts.l2vpn_evpn.redistributes:
306
- if red.policy:
307
- yield red.policy
292
+ for family_opts in (
293
+ opts.ipv4_unicast,
294
+ opts.ipv6_unicast,
295
+ opts.ipv4_labeled_unicast,
296
+ opts.ipv6_labeled_unicast,
297
+ opts.l2vpn_evpn,
298
+ ):
299
+ for red in family_opts.redistributes:
300
+ if red.policy:
301
+ yield red.policy
302
+ if family_opts.aggregate and family_opts.aggregate.policy:
303
+ yield family_opts.aggregate.policy
308
304
 
309
305
 
310
306
  def extract_policies(config: BgpConfig) -> Sequence[str]:
@@ -8,7 +8,6 @@ from .peer_models import MeshPeerGroup
8
8
  class Aggregate(BaseMeshModel):
9
9
  policy: str
10
10
  routes: Annotated[tuple[str, ...], Concat()]
11
- export_policy: str
12
11
  as_path: str
13
12
  reference: str
14
13
  suppress: bool
@@ -22,7 +21,7 @@ class FamilyOptions(BaseMeshModel):
22
21
  super().__init__(**kwargs)
23
22
  family: Family
24
23
  vrf_name: str
25
- multipath: int = 0
24
+ multipath: int
26
25
  global_multipath: int
27
26
  aggregate: Annotated[Aggregate, Merge()]
28
27
  redistributes: Annotated[tuple[Redistribute, ...], Concat()]
@@ -144,8 +144,17 @@ class MeshExecutor:
144
144
  for rule in rules:
145
145
  handler_name = self._handler_name(rule.handler)
146
146
  if rule.direct_order:
147
+ if rule.name_right not in neigbors:
148
+ print(list(neigbors), flush=True)
149
+ raise ValueError(
150
+ f"Device `{device.fqdn}` has no neighbor `{rule.name_right}` required for `{handler_name}`. {list(neigbors)}",
151
+ )
147
152
  neighbor_device = neigbors[rule.name_right]
148
153
  else:
154
+ if rule.name_left not in neigbors:
155
+ raise ValueError(
156
+ f"Device `{device.fqdn}` has no neighbor `{rule.name_left}` required for `{handler_name}`",
157
+ )
149
158
  neighbor_device = neigbors[rule.name_left]
150
159
  all_connected_ports = [
151
160
  (p1.name, p2.name)
@@ -208,11 +208,11 @@ class MeshRulesRegistry:
208
208
 
209
209
  def lookup_direct(self, device: str, neighbors: list[str]) -> list[MatchedDirectPair]:
210
210
  found = []
211
- device = self._normalize_host(device)
211
+ device_norm = self._normalize_host(device)
212
212
  for neighbor in neighbors:
213
- neighbor = self._normalize_host(neighbor)
213
+ neighbor_norm = self._normalize_host(neighbor)
214
214
  for rule in self.direct_rules:
215
- if args := rule.matcher.match_pair(device, neighbor):
215
+ if args := rule.matcher.match_pair(device_norm, neighbor_norm):
216
216
  found.append(MatchedDirectPair(
217
217
  handler=rule.handler,
218
218
  port_processor=rule.port_processor,
@@ -222,7 +222,7 @@ class MeshRulesRegistry:
222
222
  match_left=args[0],
223
223
  match_right=args[1],
224
224
  ))
225
- if args := rule.matcher.match_pair(neighbor, device):
225
+ if args := rule.matcher.match_pair(neighbor_norm, device_norm):
226
226
  found.append(MatchedDirectPair(
227
227
  handler=rule.handler,
228
228
  port_processor=rule.port_processor,
@@ -238,11 +238,11 @@ class MeshRulesRegistry:
238
238
 
239
239
  def lookup_indirect(self, device: str, devices: list[str]) -> list[MatchedIndirectPair]:
240
240
  found = []
241
- device = self._normalize_host(device)
241
+ device_norm = self._normalize_host(device)
242
242
  for other_device in devices:
243
- other_device = self._normalize_host(other_device)
243
+ other_device_norm = self._normalize_host(other_device)
244
244
  for rule in self.indirect_rules:
245
- if args := rule.matcher.match_pair(device, other_device):
245
+ if args := rule.matcher.match_pair(device_norm, other_device_norm):
246
246
  found.append(MatchedIndirectPair(
247
247
  handler=rule.handler,
248
248
  direct_order=True,
@@ -251,7 +251,7 @@ class MeshRulesRegistry:
251
251
  match_left=args[0],
252
252
  match_right=args[1],
253
253
  ))
254
- if args := rule.matcher.match_pair(other_device, device):
254
+ if args := rule.matcher.match_pair(other_device_norm, device_norm):
255
255
  found.append(MatchedIndirectPair(
256
256
  handler=rule.handler,
257
257
  direct_order=False,
@@ -266,9 +266,9 @@ class MeshRulesRegistry:
266
266
 
267
267
  def lookup_virtual(self, device: str) -> list[MatchedVirtualPair]:
268
268
  found = []
269
- device = self._normalize_host(device)
269
+ device_norm = self._normalize_host(device)
270
270
  for rule in self.virtual_rules:
271
- if args := rule.matcher.match_one(device):
271
+ if args := rule.matcher.match_one(device_norm):
272
272
  found.append(MatchedVirtualPair(
273
273
  handler=rule.handler,
274
274
  match=args,
@@ -280,9 +280,9 @@ class MeshRulesRegistry:
280
280
 
281
281
  def lookup_global(self, device: str) -> list[MatchedGlobal]:
282
282
  found = []
283
- device = self._normalize_host(device)
283
+ device_norm = self._normalize_host(device)
284
284
  for rule in self.global_rules:
285
- if args := rule.matcher.match_one(device):
285
+ if args := rule.matcher.match_one(device_norm):
286
286
  found.append(MatchedGlobal(
287
287
  handler=rule.handler,
288
288
  match=args,
@@ -0,0 +1,42 @@
1
+ from annet.annlib.types import Op
2
+
3
+ from annet.rulebook import common
4
+
5
+
6
+ def mtu(rule, key, diff, **kwargs):
7
+ """
8
+ Удаляем mtu без указания значения
9
+ """
10
+ if diff[Op.REMOVED]:
11
+ yield (False, "no mtu", None)
12
+ elif diff[Op.ADDED]:
13
+ yield from common.default(rule, key, diff, **kwargs)
14
+
15
+
16
+ def sflow(rule, key, diff, **kwargs):
17
+ """
18
+ Команда sflow sampling-rate * direction ingress max-header-size *
19
+ сносится без указания sampling-rate и max-header-size
20
+ """
21
+ result = common.default(rule, key, diff, **kwargs)
22
+ for op, cmd, ch in result:
23
+ if diff[Op.REMOVED]:
24
+ if "ingress" in diff[Op.REMOVED][0]["row"]:
25
+ yield (op, "no sflow sampling-rate direction ingress", ch)
26
+ elif "egress" in diff[Op.REMOVED][0]["row"]:
27
+ yield (op, "no sflow sampling-rate direction egress", ch)
28
+ else:
29
+ yield (op, cmd, ch)
30
+ return result
31
+
32
+
33
+ def lldp(rule, key, diff, **kwargs):
34
+ """
35
+ Не удаляем все что начинается с set, т.к. set перезаписывает предыдущий конфиг
36
+ """
37
+ result = common.default(rule, key, diff, **kwargs)
38
+ for op, cmd, ch in result:
39
+ if diff[Op.REMOVED] and "set lldp" in cmd:
40
+ pass
41
+ else:
42
+ yield (op, cmd, ch)
@@ -1,6 +1,6 @@
1
1
  # Операторы:
2
- # * Один аргумент в undo
3
- # ~ Несколько аргументов (минимум один) в undo
2
+ # * Один аргумент в no
3
+ # ~ Несколько аргументов (минимум один) в no
4
4
  #
5
5
  # Параметры:
6
6
  # %global Команда действует на любом уровне ниже
@@ -11,8 +11,13 @@
11
11
  # Сделано в основном для того чтобы генерировать специальные команды для наливки
12
12
  # -----
13
13
  # Physical
14
+ sflow *
15
+
14
16
  interface */(ce|xe)[0-9\/]+$/ %logic=common.permanent %diff_logic=cisco.iface.diff
15
17
  ipv6 address *
16
- mtu *
18
+ mtu * %logic=b4com.iface.mtu
19
+ sflow * %logic=b4com.iface.sflow
17
20
  lldp-agent
18
- ~ %rewrite %global
21
+ ~ %global %logic=b4com.iface.lldp
22
+ !dcbx *
23
+ !exit
@@ -32,7 +32,7 @@ undo route-distinguisher *
32
32
  dialog: Warning: All VPN targets of this EVPN instance and all EVPN address family-related configurations under BGP will be deleted. Continue? [Y/N]: ::: Y
33
33
 
34
34
  save
35
- dialog: Warning: The current configuration will be written to the device. Continue? [Y/N] ::: Y
35
+ dialog: /Warning: The current configuration will be written to the device. Continue\? \[Y/N\]:?/ ::: Y
36
36
  dialog: Warning: The current configuration will be written to the device. Are you sure to continue? [Y/N]: ::: Y
37
37
  dialog: Are you sure to continue?[Y/N] ::: Y
38
38
  dialog: Are you sure to continue? [Y/N] ::: Y
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 0.16.33
3
+ Version: 0.16.35
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -23,7 +23,7 @@ Requires-Dist: yarl>=1.8.2
23
23
  Requires-Dist: adaptix==3.0.0b7
24
24
  Requires-Dist: dataclass-rest==0.4
25
25
  Provides-Extra: netbox
26
- Requires-Dist: annetbox[sync]>=0.1.10; extra == "netbox"
26
+ Requires-Dist: annetbox[sync]>=0.1.12; extra == "netbox"
27
27
  Dynamic: home-page
28
28
  Dynamic: license
29
29
  Dynamic: provides-extra
@@ -130,6 +130,7 @@ annet/rulebook/aruba/ap_env.py
130
130
  annet/rulebook/aruba/misc.py
131
131
  annet/rulebook/b4com/__init__.py
132
132
  annet/rulebook/b4com/file.py
133
+ annet/rulebook/b4com/iface.py
133
134
  annet/rulebook/cisco/__init__.py
134
135
  annet/rulebook/cisco/iface.py
135
136
  annet/rulebook/cisco/misc.py
@@ -15,4 +15,4 @@ adaptix==3.0.0b7
15
15
  dataclass-rest==0.4
16
16
 
17
17
  [netbox]
18
- annetbox[sync]>=0.1.10
18
+ annetbox[sync]>=0.1.12
@@ -44,7 +44,7 @@ if __name__ == "__main__":
44
44
  ],
45
45
  },
46
46
  extras_require={
47
- "netbox": ["annetbox[sync]>=0.1.10"],
47
+ "netbox": ["annetbox[sync]>=0.1.12"],
48
48
  },
49
49
  python_requires=">=3.10",
50
50
  install_requires=requirements(),
@@ -1,26 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import List, Union, Iterable, Optional
3
-
4
- from annet.storage import Query
5
-
6
-
7
- @dataclass
8
- class NetboxQuery(Query):
9
- query: List[str]
10
-
11
- @classmethod
12
- def new(
13
- cls, query: Union[str, Iterable[str]],
14
- hosts_range: Optional[slice] = None,
15
- ) -> "NetboxQuery":
16
- if hosts_range is not None:
17
- raise ValueError("host_range is not supported")
18
- return cls(query=list(query))
19
-
20
- @property
21
- def globs(self):
22
- # We process every query host as a glob
23
- return self.query
24
-
25
- def is_empty(self) -> bool:
26
- return len(self.query) == 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes