annet 2.3.1__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

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