annet 1.0.4__tar.gz → 1.1.1__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 (197) hide show
  1. {annet-1.0.4/annet.egg-info → annet-1.1.1}/PKG-INFO +2 -3
  2. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/query.py +3 -1
  3. {annet-1.0.4 → annet-1.1.1}/annet/annlib/patching.py +17 -8
  4. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/ordering.py +11 -1
  5. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/platform.py +2 -2
  6. {annet-1.0.4 → annet-1.1.1}/annet/annlib/tabparser.py +91 -30
  7. annet-1.1.1/annet/executor.py +172 -0
  8. annet-1.1.1/annet/rulebook/juniper/__init__.py +156 -0
  9. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.order +12 -12
  10. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.order +2 -2
  11. annet-1.1.1/annet/rulebook/texts/juniper.order +4 -0
  12. {annet-1.0.4 → annet-1.1.1/annet.egg-info}/PKG-INFO +2 -3
  13. {annet-1.0.4 → annet-1.1.1}/annet.egg-info/SOURCES.txt +1 -1
  14. {annet-1.0.4 → annet-1.1.1}/annet.egg-info/requires.txt +1 -2
  15. {annet-1.0.4 → annet-1.1.1}/requirements.txt +0 -1
  16. {annet-1.0.4 → annet-1.1.1}/setup.py +1 -1
  17. annet-1.0.4/annet/executor.py +0 -551
  18. annet-1.0.4/annet/rulebook/juniper/__init__.py +0 -107
  19. annet-1.0.4/annet/rulebook/ribbon/__init__.py +0 -12
  20. {annet-1.0.4 → annet-1.1.1}/AUTHORS +0 -0
  21. {annet-1.0.4 → annet-1.1.1}/LICENSE +0 -0
  22. {annet-1.0.4 → annet-1.1.1}/MANIFEST.in +0 -0
  23. {annet-1.0.4 → annet-1.1.1}/README.md +0 -0
  24. {annet-1.0.4 → annet-1.1.1}/annet/__init__.py +0 -0
  25. {annet-1.0.4 → annet-1.1.1}/annet/adapters/__init__.py +0 -0
  26. {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/__init__.py +0 -0
  27. {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/stub/__init__.py +0 -0
  28. {annet-1.0.4 → annet-1.1.1}/annet/adapters/fetchers/stub/fetcher.py +0 -0
  29. {annet-1.0.4 → annet-1.1.1}/annet/adapters/file/__init__.py +0 -0
  30. {annet-1.0.4 → annet-1.1.1}/annet/adapters/file/provider.py +0 -0
  31. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/__init__.py +0 -0
  32. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/__init__.py +0 -0
  33. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/client.py +0 -0
  34. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/manufacturer.py +0 -0
  35. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/models.py +0 -0
  36. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/status_client.py +0 -0
  37. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/common/storage_opts.py +0 -0
  38. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/provider.py +0 -0
  39. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v24/__init__.py +0 -0
  40. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v24/storage.py +0 -0
  41. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v37/__init__.py +0 -0
  42. {annet-1.0.4 → annet-1.1.1}/annet/adapters/netbox/v37/storage.py +0 -0
  43. {annet-1.0.4 → annet-1.1.1}/annet/annet.py +0 -0
  44. {annet-1.0.4 → annet-1.1.1}/annet/annlib/__init__.py +0 -0
  45. {annet-1.0.4 → annet-1.1.1}/annet/annlib/command.py +0 -0
  46. {annet-1.0.4 → annet-1.1.1}/annet/annlib/diff.py +0 -0
  47. {annet-1.0.4 → annet-1.1.1}/annet/annlib/errors.py +0 -0
  48. {annet-1.0.4 → annet-1.1.1}/annet/annlib/filter_acl.py +0 -0
  49. {annet-1.0.4 → annet-1.1.1}/annet/annlib/jsontools.py +0 -0
  50. {annet-1.0.4 → annet-1.1.1}/annet/annlib/lib.py +0 -0
  51. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/__init__.py +0 -0
  52. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/db.py +0 -0
  53. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/devdb/__init__.py +0 -0
  54. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  55. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/__init__.py +0 -0
  56. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/dump.py +0 -0
  57. {annet-1.0.4 → annet-1.1.1}/annet/annlib/netdev/views/hardware.py +0 -0
  58. {annet-1.0.4 → annet-1.1.1}/annet/annlib/output.py +0 -0
  59. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/__init__.py +0 -0
  60. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/acl.py +0 -0
  61. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/deploying.py +0 -0
  62. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rbparser/syntax.py +0 -0
  63. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rulebook/__init__.py +0 -0
  64. {annet-1.0.4 → annet-1.1.1}/annet/annlib/rulebook/common.py +0 -0
  65. {annet-1.0.4 → annet-1.1.1}/annet/annlib/types.py +0 -0
  66. {annet-1.0.4 → annet-1.1.1}/annet/api/__init__.py +0 -0
  67. {annet-1.0.4 → annet-1.1.1}/annet/argparse.py +0 -0
  68. {annet-1.0.4 → annet-1.1.1}/annet/bgp_models.py +0 -0
  69. {annet-1.0.4 → annet-1.1.1}/annet/cli.py +0 -0
  70. {annet-1.0.4 → annet-1.1.1}/annet/cli_args.py +0 -0
  71. {annet-1.0.4 → annet-1.1.1}/annet/configs/context.yml +0 -0
  72. {annet-1.0.4 → annet-1.1.1}/annet/configs/logging.yaml +0 -0
  73. {annet-1.0.4 → annet-1.1.1}/annet/connectors.py +0 -0
  74. {annet-1.0.4 → annet-1.1.1}/annet/deploy.py +0 -0
  75. {annet-1.0.4 → annet-1.1.1}/annet/deploy_ui.py +0 -0
  76. {annet-1.0.4 → annet-1.1.1}/annet/diff.py +0 -0
  77. {annet-1.0.4 → annet-1.1.1}/annet/filtering.py +0 -0
  78. {annet-1.0.4 → annet-1.1.1}/annet/gen.py +0 -0
  79. {annet-1.0.4 → annet-1.1.1}/annet/generators/__init__.py +0 -0
  80. {annet-1.0.4 → annet-1.1.1}/annet/generators/base.py +0 -0
  81. {annet-1.0.4 → annet-1.1.1}/annet/generators/common/__init__.py +0 -0
  82. {annet-1.0.4 → annet-1.1.1}/annet/generators/common/initial.py +0 -0
  83. {annet-1.0.4 → annet-1.1.1}/annet/generators/entire.py +0 -0
  84. {annet-1.0.4 → annet-1.1.1}/annet/generators/exceptions.py +0 -0
  85. {annet-1.0.4 → annet-1.1.1}/annet/generators/jsonfragment.py +0 -0
  86. {annet-1.0.4 → annet-1.1.1}/annet/generators/partial.py +0 -0
  87. {annet-1.0.4 → annet-1.1.1}/annet/generators/perf.py +0 -0
  88. {annet-1.0.4 → annet-1.1.1}/annet/generators/ref.py +0 -0
  89. {annet-1.0.4 → annet-1.1.1}/annet/generators/result.py +0 -0
  90. {annet-1.0.4 → annet-1.1.1}/annet/hardware.py +0 -0
  91. {annet-1.0.4 → annet-1.1.1}/annet/implicit.py +0 -0
  92. {annet-1.0.4 → annet-1.1.1}/annet/lib.py +0 -0
  93. {annet-1.0.4 → annet-1.1.1}/annet/mesh/__init__.py +0 -0
  94. {annet-1.0.4 → annet-1.1.1}/annet/mesh/basemodel.py +0 -0
  95. {annet-1.0.4 → annet-1.1.1}/annet/mesh/device_models.py +0 -0
  96. {annet-1.0.4 → annet-1.1.1}/annet/mesh/executor.py +0 -0
  97. {annet-1.0.4 → annet-1.1.1}/annet/mesh/match_args.py +0 -0
  98. {annet-1.0.4 → annet-1.1.1}/annet/mesh/models_converter.py +0 -0
  99. {annet-1.0.4 → annet-1.1.1}/annet/mesh/peer_models.py +0 -0
  100. {annet-1.0.4 → annet-1.1.1}/annet/mesh/port_processor.py +0 -0
  101. {annet-1.0.4 → annet-1.1.1}/annet/mesh/registry.py +0 -0
  102. {annet-1.0.4 → annet-1.1.1}/annet/output.py +0 -0
  103. {annet-1.0.4 → annet-1.1.1}/annet/parallel.py +0 -0
  104. {annet-1.0.4 → annet-1.1.1}/annet/patching.py +0 -0
  105. {annet-1.0.4 → annet-1.1.1}/annet/reference.py +0 -0
  106. {annet-1.0.4 → annet-1.1.1}/annet/rpl/__init__.py +0 -0
  107. {annet-1.0.4 → annet-1.1.1}/annet/rpl/action.py +0 -0
  108. {annet-1.0.4 → annet-1.1.1}/annet/rpl/condition.py +0 -0
  109. {annet-1.0.4 → annet-1.1.1}/annet/rpl/match_builder.py +0 -0
  110. {annet-1.0.4 → annet-1.1.1}/annet/rpl/policy.py +0 -0
  111. {annet-1.0.4 → annet-1.1.1}/annet/rpl/result.py +0 -0
  112. {annet-1.0.4 → annet-1.1.1}/annet/rpl/routemap.py +0 -0
  113. {annet-1.0.4 → annet-1.1.1}/annet/rpl/statement_builder.py +0 -0
  114. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/__init__.py +0 -0
  115. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/aspath.py +0 -0
  116. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/community.py +0 -0
  117. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/cumulus_frr.py +0 -0
  118. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/entities.py +0 -0
  119. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/execute.py +0 -0
  120. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/policy.py +0 -0
  121. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/prefix_lists.py +0 -0
  122. {annet-1.0.4 → annet-1.1.1}/annet/rpl_generators/rd.py +0 -0
  123. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/__init__.py +0 -0
  124. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/__init__.py +0 -0
  125. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/aaa.py +0 -0
  126. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/arista/iface.py +0 -0
  127. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/__init__.py +0 -0
  128. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/ap_env.py +0 -0
  129. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/aruba/misc.py +0 -0
  130. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/__init__.py +0 -0
  131. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/file.py +0 -0
  132. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/b4com/iface.py +0 -0
  133. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/__init__.py +0 -0
  134. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/iface.py +0 -0
  135. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/misc.py +0 -0
  136. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/cisco/vlandb.py +0 -0
  137. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/common.py +0 -0
  138. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/deploying.py +0 -0
  139. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/__init__.py +0 -0
  140. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/aaa.py +0 -0
  141. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/bgp.py +0 -0
  142. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/iface.py +0 -0
  143. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/misc.py +0 -0
  144. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/huawei/vlandb.py +0 -0
  145. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/nexus/__init__.py +0 -0
  146. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/nexus/iface.py +0 -0
  147. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/patching.py +0 -0
  148. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/routeros/__init__.py +0 -0
  149. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/routeros/file.py +0 -0
  150. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.deploy +0 -0
  151. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/arista.rul +0 -0
  152. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.deploy +0 -0
  153. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.order +0 -0
  154. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/aruba.rul +0 -0
  155. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.deploy +0 -0
  156. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.order +0 -0
  157. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/b4com.rul +0 -0
  158. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.deploy +0 -0
  159. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.order +0 -0
  160. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/cisco.rul +0 -0
  161. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.deploy +0 -0
  162. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/huawei.rul +0 -0
  163. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/juniper.rul +0 -0
  164. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.deploy +0 -0
  165. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.order +0 -0
  166. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nexus.rul +0 -0
  167. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/nokia.rul +0 -0
  168. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.deploy +0 -0
  169. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.order +0 -0
  170. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/optixtrans.rul +0 -0
  171. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.deploy +0 -0
  172. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.order +0 -0
  173. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/pc.rul +0 -0
  174. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/ribbon.deploy +0 -0
  175. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/ribbon.rul +0 -0
  176. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/routeros.order +0 -0
  177. {annet-1.0.4 → annet-1.1.1}/annet/rulebook/texts/routeros.rul +0 -0
  178. {annet-1.0.4 → annet-1.1.1}/annet/storage.py +0 -0
  179. {annet-1.0.4 → annet-1.1.1}/annet/tabparser.py +0 -0
  180. {annet-1.0.4 → annet-1.1.1}/annet/text_term_format.py +0 -0
  181. {annet-1.0.4 → annet-1.1.1}/annet/tracing.py +0 -0
  182. {annet-1.0.4 → annet-1.1.1}/annet/types.py +0 -0
  183. {annet-1.0.4 → annet-1.1.1}/annet.egg-info/dependency_links.txt +0 -0
  184. {annet-1.0.4 → annet-1.1.1}/annet.egg-info/entry_points.txt +0 -0
  185. {annet-1.0.4 → annet-1.1.1}/annet.egg-info/top_level.txt +0 -0
  186. {annet-1.0.4 → annet-1.1.1}/annet_generators/__init__.py +0 -0
  187. {annet-1.0.4 → annet-1.1.1}/annet_generators/example/__init__.py +0 -0
  188. {annet-1.0.4 → annet-1.1.1}/annet_generators/example/lldp.py +0 -0
  189. {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/__init__.py +0 -0
  190. {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/bgp.py +0 -0
  191. {annet-1.0.4 → annet-1.1.1}/annet_generators/mesh_example/mesh_logic.py +0 -0
  192. {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/__init__.py +0 -0
  193. {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/generator.py +0 -0
  194. {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/items.py +0 -0
  195. {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/mesh.py +0 -0
  196. {annet-1.0.4 → annet-1.1.1}/annet_generators/rpl_example/route_policy.py +0 -0
  197. {annet-1.0.4 → annet-1.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 1.0.4
3
+ Version: 1.1.1
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -15,7 +15,6 @@ Requires-Dist: PyYAML>=6.0.1
15
15
  Requires-Dist: Pygments>=2.14.0
16
16
  Requires-Dist: Mako>=1.2.4
17
17
  Requires-Dist: Jinja2>=3.1.2
18
- Requires-Dist: psutil>=5.8.0
19
18
  Requires-Dist: packaging>=23.2
20
19
  Requires-Dist: contextlog>=1.1
21
20
  Requires-Dist: valkit>=0.1.4
@@ -23,7 +22,7 @@ Requires-Dist: yarl>=1.8.2
23
22
  Requires-Dist: adaptix==3.0.0b7
24
23
  Requires-Dist: dataclass-rest==0.4
25
24
  Provides-Extra: netbox
26
- Requires-Dist: annetbox[sync]>=0.2.0; extra == "netbox"
25
+ Requires-Dist: annetbox[sync]>=0.2.1; extra == "netbox"
27
26
  Dynamic: home-page
28
27
  Dynamic: license
29
28
  Dynamic: provides-extra
@@ -5,7 +5,7 @@ from typing import cast, List, Union, Iterable, Optional, TypedDict
5
5
  from annet.storage import Query
6
6
 
7
7
  FIELD_VALUE_SEPARATOR = ":"
8
- ALLOWED_GLOB_GROUPS = ["site", "tag", "role", "device_type"]
8
+ ALLOWED_GLOB_GROUPS = ["site", "tag", "role", "device_type", "status", "tenant"]
9
9
 
10
10
 
11
11
  class Filter(TypedDict, total=False):
@@ -14,6 +14,8 @@ class Filter(TypedDict, total=False):
14
14
  role: list[str]
15
15
  name: list[str]
16
16
  device_type: list[str]
17
+ status: list[str]
18
+ tenant: list[str]
17
19
 
18
20
 
19
21
  @dataclass
@@ -2,7 +2,7 @@ import copy
2
2
  import operator
3
3
  import textwrap
4
4
  from collections import OrderedDict as odict
5
- from typing import ( # pylint: disable=unused-import
5
+ from typing import (
6
6
  Any,
7
7
  Dict,
8
8
  Iterator,
@@ -177,7 +177,7 @@ class Orderer:
177
177
  def rule_weight(self, row, rule, regexp_key):
178
178
  return len(set(row).intersection(set(rule["attrs"][regexp_key].pattern))) / len(row)
179
179
 
180
- def get_order(self, row, cmd_direct):
180
+ def get_order(self, row, cmd_direct, scope: str | None = None):
181
181
  f_order = None
182
182
  f_weight = 0
183
183
  f_rule = ""
@@ -186,6 +186,15 @@ class Orderer:
186
186
  block_exit = platform.VENDOR_EXIT[self.vendor]
187
187
 
188
188
  for (order, (raw_rule, rule)) in enumerate(ordering.items()):
189
+ if (
190
+ (rule_scope := rule["attrs"]["scope"]) is not None
191
+ and scope not in rule_scope
192
+ ):
193
+ continue
194
+
195
+ if rule["attrs"]["global"]:
196
+ children.append((raw_rule, rule))
197
+
189
198
  direct_matched = bool(rule["attrs"]["direct_regexp"].match(row))
190
199
  if not rule["attrs"]["order_reverse"] and (direct_matched or rule["attrs"]["reverse_regexp"].match(row)):
191
200
  # если не указано order_reverse - правило считается прямым
@@ -395,7 +404,7 @@ def make_patch(pre, rb, hw, add_comments, orderer=None, _root_pre=None, do_commi
395
404
  for (key, diff) in content["items"].items():
396
405
  # чтобы logic не мог поменять атрибуты
397
406
  rule_pre = content.copy()
398
- attrs = rule_pre["attrs"].copy()
407
+ attrs = copy.deepcopy(rule_pre["attrs"])
399
408
 
400
409
  iterable = attrs["logic"](
401
410
  rule=attrs,
@@ -416,7 +425,7 @@ def make_patch(pre, rb, hw, add_comments, orderer=None, _root_pre=None, do_commi
416
425
  patch_row = "%s %s" % (row, comments)
417
426
 
418
427
  # pylint: disable=unused-variable
419
- (order, order_direct, ordering, order_rule) = orderer.get_order(row, direct)
428
+ (order, order_direct, ordering, order_rule) = orderer.get_order(row, direct, scope="patch")
420
429
  fmt_row = patch_row
421
430
  # fmt_row += " # %s" % str(order_rule) # uncomment to debug ordering
422
431
 
@@ -544,8 +553,7 @@ def _select_match(matches, rules):
544
553
  for (rule, is_cr_allowed) in map(operator.itemgetter(0), matches):
545
554
  if is_cr_allowed:
546
555
  local_children = merge_dicts(local_children, rule["children"]["local"])
547
- # optional break on is_cr_allowed==False?
548
-
556
+ # optional break on is_cr_allowed==False?
549
557
  global_children = merge_dicts(global_children, rule["children"]["global"])
550
558
 
551
559
  global_children = merge_dicts(global_children, rules["global"])
@@ -555,9 +563,10 @@ def _select_match(matches, rules):
555
563
  "global": global_children,
556
564
  }
557
565
 
558
- match = {"attrs": f_rule["attrs"]}
566
+ match = {"attrs": copy.deepcopy(f_rule["attrs"])}
559
567
  match.update(f_other)
560
- return (match, children_rules)
568
+
569
+ return match, children_rules
561
570
 
562
571
 
563
572
  def _rules_local_global(rules):
@@ -2,7 +2,7 @@ import functools
2
2
  import re
3
3
  from collections import OrderedDict as odict
4
4
 
5
- from valkit.common import valid_bool
5
+ from valkit.common import valid_bool, valid_string_list
6
6
 
7
7
  from . import platform, syntax
8
8
 
@@ -16,6 +16,14 @@ def compile_ordering_text(text, vendor):
16
16
  "validator": valid_bool,
17
17
  "default": False,
18
18
  },
19
+ "global": {
20
+ "validator": valid_bool,
21
+ "default": False,
22
+ },
23
+ "scope": {
24
+ "validator": valid_string_list,
25
+ "default": None,
26
+ }
19
27
  }),
20
28
  reverse_prefix=platform.VENDOR_REVERSES[vendor],
21
29
  )
@@ -44,6 +52,8 @@ def _compile_ordering(tree, reverse_prefix):
44
52
  syntax.compile_row_regexp(re.sub(r"^%s\s+" % (reverse_prefix), "", attrs["row"]))
45
53
  ),
46
54
  "order_reverse": attrs["params"]["order_reverse"],
55
+ "global": attrs["params"]["global"],
56
+ "scope": attrs["params"]["scope"],
47
57
  "raw_rule": attrs["raw_rule"],
48
58
  "context": attrs["context"],
49
59
  },
@@ -25,7 +25,7 @@ VENDOR_DIFF = {
25
25
  "routeros": "common.default_diff",
26
26
  "aruba": "aruba.default_diff",
27
27
  "pc": "common.default_diff",
28
- "ribbon": "ribbon.default_diff",
28
+ "ribbon": "common.default_diff",
29
29
  "b4com": "common.default_diff",
30
30
  }
31
31
 
@@ -40,7 +40,7 @@ VENDOR_DIFF_ORDERED = {
40
40
  "routeros": "common.ordered_diff",
41
41
  "aruba": "common.ordered_diff",
42
42
  "pc": "common.ordered_diff",
43
- "ribbon": "ribbon.default_diff",
43
+ "ribbon": "common.ordered_diff",
44
44
  "b4com": "common.ordered_diff",
45
45
  }
46
46
 
@@ -1,6 +1,8 @@
1
1
  import dataclasses
2
2
  import itertools
3
+ import json
3
4
  import re
5
+ import textwrap
4
6
  from collections import OrderedDict as odict
5
7
  from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union, List
6
8
 
@@ -70,10 +72,10 @@ class CommonFormatter:
70
72
  self._block_end = ""
71
73
  self._statement_end = ""
72
74
 
73
- def split(self, text):
75
+ def split(self, text: str):
74
76
  return list(filter(None, text.split("\n")))
75
77
 
76
- def join(self, config):
78
+ def join(self, config: "PatchTree"):
77
79
  return "\n".join(
78
80
  _filtered_block_marks(
79
81
  self._indent_blocks(self._blocks(config, is_patch=False))
@@ -86,14 +88,14 @@ class CommonFormatter:
86
88
  def diff(self, diff):
87
89
  return list(self.diff_generator(diff))
88
90
 
89
- def patch(self, patch):
91
+ def patch(self, patch: "PatchTree") -> str:
90
92
  return "\n".join(
91
93
  _filtered_block_marks(
92
94
  self._indent_blocks(self._blocks(patch, is_patch=True))
93
95
  )
94
96
  )
95
97
 
96
- def cmd_paths(self, patch):
98
+ def cmd_paths(self, patch: "PatchTree") -> odict:
97
99
  ret = odict()
98
100
  path = []
99
101
  for row, context in self.blocks_and_context(patch, is_patch=True):
@@ -175,7 +177,7 @@ class CommonFormatter:
175
177
  )
176
178
  yield BlockEnd, None
177
179
 
178
- def _blocks(self, tree, is_patch):
180
+ def _blocks(self, tree: "PatchTree", is_patch: bool):
179
181
  for row, _context in self.blocks_and_context(tree, is_patch):
180
182
  yield row
181
183
 
@@ -386,7 +388,32 @@ class AsrFormatter(BlockExitFormatter):
386
388
 
387
389
 
388
390
  class JuniperFormatter(CommonFormatter):
389
- patch_set_prefix = "set "
391
+ patch_set_prefix = "set"
392
+
393
+ @dataclasses.dataclass
394
+ class Comment:
395
+ begin = "/*"
396
+ end = "*/"
397
+
398
+ row: str
399
+ comment: str
400
+
401
+ def __post_init__(self):
402
+ self.row = self.row.strip()
403
+ self.comment = self.comment.strip()
404
+
405
+ @classmethod
406
+ def loads(cls, value: str):
407
+ return cls(
408
+ **json.loads(
409
+ value.removeprefix(cls.begin)
410
+ .removesuffix(cls.end)
411
+ .strip()
412
+ )
413
+ )
414
+
415
+ def dumps(self):
416
+ return json.dumps({"row": self.row, "comment": self.comment})
390
417
 
391
418
  def __init__(self, indent=" "):
392
419
  super().__init__(indent)
@@ -395,20 +422,32 @@ class JuniperFormatter(CommonFormatter):
395
422
  self._statement_end = ";"
396
423
  self._endofline_comment = "; ##"
397
424
 
398
- def split(self, text):
399
- sub_regexs = (
425
+ self._sub_regexs = (
400
426
  (re.compile(self._block_begin + r"\s*" + self._block_end + r"$"), ""), # collapse empty blocks
401
427
  (re.compile(self._block_begin + "(\t# .+)?$"), ""),
402
428
  (re.compile(self._statement_end + r"$"), ""),
403
429
  (re.compile(r"\s*" + self._block_end + "(\t# .+)?$"), ""),
404
430
  (re.compile(self._endofline_comment + r".*$"), ""),
405
431
  )
406
- split = []
407
- for line in text.split("\n"):
408
- for (regex, repl_line) in sub_regexs:
409
- line = regex.sub(repl_line, line)
410
- split.append(line)
411
- return list(filter(None, split))
432
+
433
+ def sub_regexs(self, value: str) -> str:
434
+ for (regex, repl_line) in self._sub_regexs:
435
+ value = regex.sub(repl_line, value)
436
+ return value
437
+
438
+ def split(self, text: str) -> list[str]:
439
+ comment_begin, comment_end = map(re.escape, (self.Comment.begin, self.Comment.end))
440
+ comment_regexp = re.compile(fr"(\s+{comment_begin})((?:(?!{comment_end}).)*)({comment_end})")
441
+
442
+ result = []
443
+ lines = text.split("\n")
444
+ for i, line in enumerate(lines):
445
+ line = self.sub_regexs(line)
446
+ if i + 1 < len(lines) and (m := comment_regexp.match(line)):
447
+ line = f"{m.group(1)} {self.Comment(self.sub_regexs(lines[i + 1]), m.group(2)).dumps()} {m.group(3)}"
448
+ result.append(line)
449
+
450
+ return list(filter(None, result))
412
451
 
413
452
  def join(self, config):
414
453
  return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
@@ -419,6 +458,13 @@ class JuniperFormatter(CommonFormatter):
419
458
  def patch_plain(self, patch):
420
459
  return list(self.cmd_paths(patch).keys())
421
460
 
461
+ def _blocks(self, tree: "PatchTree", is_patch: bool):
462
+ for row in super()._blocks(tree, is_patch):
463
+ if isinstance(row, str) and row.startswith(self.Comment.begin):
464
+ yield f"{self.Comment.begin} {self.Comment.loads(row).comment} {self.Comment.end}"
465
+ else:
466
+ yield row
467
+
422
468
  def _formatted_blocks(self, blocks):
423
469
  level = 0
424
470
  line = None
@@ -430,33 +476,48 @@ class JuniperFormatter(CommonFormatter):
430
476
  elif new_line is BlockEnd:
431
477
  level -= 1
432
478
  if isinstance(line, str):
433
- yield line + self._statement_end
479
+ yield line + ("" if line.endswith(self.Comment.end) else self._statement_end)
434
480
  yield self._indent * level + self._block_end
435
481
  elif isinstance(line, str):
436
- yield line + self._statement_end
482
+ yield line + ("" if line.endswith(self.Comment.end) else self._statement_end)
437
483
  line = new_line
438
484
  if isinstance(line, str):
439
485
  yield line + self._statement_end
440
486
 
441
- def cmd_paths(self, patch, _prev=""):
487
+ def cmd_paths(self, patch, _prev=tuple()):
442
488
  commands = odict()
443
489
  for item in patch.itms:
444
490
  key, childs, context = item.row, item.child, item.context
491
+
445
492
  if childs:
446
- for k, v in self.cmd_paths(childs, _prev + " " + key).items():
493
+ for k, v in self.cmd_paths(childs, (*_prev, key.strip())).items():
447
494
  commands[k] = v
448
495
  else:
449
- if key.startswith("delete"):
450
- cmd = "delete" + _prev + " " + key.replace("delete", "", 1).strip()
496
+ if "comment" in context:
497
+ value = (
498
+ ""
499
+ if key.startswith("delete")
500
+ else context["comment"]
501
+ )
502
+
503
+ cmd = "\n".join(
504
+ (
505
+ "edit " + " ".join(_prev),
506
+ " ".join(("annotate", context["row"].split(" ")[0], f'"{value}"')),
507
+ "exit"
508
+ )
509
+ )
510
+ elif key.startswith("delete"):
511
+ cmd = " ".join(("delete", *_prev, key.replace("delete", "", 1).strip()))
451
512
  elif key.startswith("activate"):
452
- cmd = "activate" + _prev + " " + key.replace("activate", "", 1).strip()
513
+ cmd = " ".join(("activate", *_prev, key.replace("activate", "", 1).strip()))
453
514
  elif key.startswith("deactivate"):
454
- cmd = "deactivate" + _prev + " " + key.replace("deactivate", "", 1).strip()
515
+ cmd = " ".join(("deactivate", *_prev, key.replace("deactivate", "", 1).strip()))
455
516
  else:
456
- cmd = (self.patch_set_prefix + _prev.strip()).strip() + " " + key
517
+ cmd = " ".join((self.patch_set_prefix, *_prev, key.strip()))
518
+
457
519
  # Expanding [ a b c ] junipers list of arguments
458
- matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
459
- if matches:
520
+ if matches := re.search(r"^(.*)\s+\[(.+)\]$", cmd):
460
521
  for c in matches.group(2).split(" "):
461
522
  if c.strip():
462
523
  cmd = " ".join([matches.group(1), c])
@@ -490,7 +551,7 @@ class JuniperList:
490
551
 
491
552
 
492
553
  class NokiaFormatter(JuniperFormatter):
493
- patch_set_prefix = "/configure "
554
+ patch_set_prefix = "/configure"
494
555
 
495
556
  def __init__(self, *args, **kwargs):
496
557
  super().__init__(*args, **kwargs)
@@ -517,18 +578,18 @@ class NokiaFormatter(JuniperFormatter):
517
578
  finish = finish if finish is not None else len(ret)
518
579
  return ret[start:finish]
519
580
 
520
- def cmd_paths(self, patch, _prev=""):
581
+ def cmd_paths(self, patch, _prev=tuple()):
521
582
  commands = odict()
522
583
  for item in patch.itms:
523
584
  key, childs, context = item.row, item.child, item.context
524
585
  if childs:
525
- for k, v in self.cmd_paths(childs, _prev + " " + key).items():
586
+ for k, v in self.cmd_paths(childs, (*_prev, key.strip())).items():
526
587
  commands[k] = v
527
588
  else:
528
589
  if key.startswith("delete"):
529
- cmd = "/configure delete" + _prev + " " + key.replace("delete", "", 1).strip()
590
+ cmd = " ".join((self.patch_set_prefix, "delete", *_prev, key.replace("delete", "", 1).strip()))
530
591
  else:
531
- cmd = self.patch_set_prefix + _prev.strip() + " " + key
592
+ cmd = " ".join((self.patch_set_prefix, *_prev, key.strip()))
532
593
  # Expanding [ a b c ] junipers list of arguments
533
594
  matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
534
595
  if matches:
@@ -0,0 +1,172 @@
1
+ import asyncio
2
+ import os
3
+ import statistics
4
+ from abc import ABC, abstractmethod
5
+ from functools import partial
6
+ from operator import itemgetter
7
+ from typing import Any, List, Optional
8
+
9
+ import colorama
10
+ from annet.annlib.command import Command, CommandList, Question # noqa: F401
11
+
12
+
13
+ class CommandResult(ABC):
14
+ @abstractmethod
15
+ def get_out(self) -> str:
16
+ pass
17
+
18
+
19
+ class ExecutorException(Exception):
20
+ def __init__(self, *args: List[Any], auxiliary: Optional[Any] = None, **kwargs: object):
21
+ self.auxiliary = auxiliary
22
+ super().__init__(*args, **kwargs)
23
+
24
+ def __repr__(self) -> str:
25
+ return "%s(args=%r,auxiliary=%s)" % (self.__class__.__name__, self.args, self.auxiliary)
26
+
27
+
28
+ class ExecException(ExecutorException):
29
+ def __init__(self, msg: str, cmd: str, res: str, **kwargs):
30
+ super().__init__(**kwargs)
31
+ self.args = msg, cmd, res
32
+ self.kwargs = kwargs
33
+ self.msg = msg
34
+ self.cmd = cmd
35
+ self.res = res
36
+
37
+ def __str__(self) -> str:
38
+ return str(self.msg)
39
+
40
+ def __repr__(self) -> str:
41
+ return "%s<%s, %s>" % (self.__class__.__name__, self.msg, self.cmd)
42
+
43
+
44
+ class BadCommand(ExecException):
45
+ pass
46
+
47
+
48
+ class NonzeroRetcode(ExecException):
49
+ pass
50
+
51
+
52
+ class CommitException(ExecException):
53
+ pass
54
+
55
+
56
+ def _show_type_summary(caption, items, total, stat_items=None):
57
+ if items:
58
+ if not stat_items:
59
+ stat = ""
60
+ else:
61
+ avg = statistics.mean(stat_items)
62
+ stat = " %(min).1f/%(max).1f/%(avg).1f/%(stdev)s (min/max/avg/stdev)" % dict(
63
+ min=min(stat_items),
64
+ max=max(stat_items),
65
+ avg=avg,
66
+ stdev="-" if len(stat_items) < 2 else "%.1f" % statistics.stdev(stat_items, xbar=avg)
67
+ )
68
+
69
+ print("%-8s %d of %d%s" % (caption, len(items), total, stat))
70
+
71
+
72
+ def show_bulk_report(hostnames, res, durations, log_dir):
73
+ total = len(hostnames)
74
+ if not total:
75
+ return
76
+
77
+ colorama.init()
78
+
79
+ print("\n====== bulk deploy report ======")
80
+
81
+ done = [host for (host, hres) in res.items() if not isinstance(hres, Exception)]
82
+ cancelled = [host for (host, hres) in res.items() if isinstance(hres, asyncio.CancelledError)]
83
+ failed = [host for (host, hres) in res.items() if isinstance(hres, Exception) and host not in cancelled]
84
+ lost = [host for host in hostnames if host not in res]
85
+ limit = 30
86
+
87
+ _show_type_summary("Done :", done, total, [durations[h] for h in done])
88
+ _print_limit(done, partial(_print_hostname, style=colorama.Fore.GREEN), limit, total)
89
+
90
+ _show_type_summary("Failed :", failed, total, [durations[h] for h in failed])
91
+
92
+ _print_limit(failed, partial(_print_failed, res=res), limit, total)
93
+
94
+ _show_type_summary("Cancelled :", cancelled, total, [durations[h] for h in cancelled if durations[h] is not None])
95
+ _print_limit(cancelled, partial(_print_hostname, style=colorama.Fore.RED), limit, total)
96
+
97
+ _show_type_summary("Lost :", lost, total)
98
+ _print_limit(lost, _print_hostname, limit, total)
99
+
100
+ err_limit = 5
101
+ if failed:
102
+ errs = {}
103
+ for hostname in failed:
104
+ fmt_err = _format_exc(res[hostname])
105
+ if fmt_err in errs:
106
+ errs[fmt_err] += 1
107
+ else:
108
+ errs[fmt_err] = 1
109
+ print("Top errors :")
110
+ for fmt_err, n in sorted(errs.items(), key=itemgetter(1), reverse=True)[:err_limit]:
111
+ print(" %-4d %s" % (n, fmt_err))
112
+ print("\n", end="")
113
+
114
+ if log_dir:
115
+ print("See deploy logs in %s/\n" % os.path.relpath(log_dir))
116
+
117
+
118
+ def _format_exc(exc):
119
+ if isinstance(exc, ExecException):
120
+ cmd = str(exc.cmd)
121
+ if len(cmd) > 50:
122
+ cmd = cmd[:50] + "~.."
123
+ return "'%s', cmd '%s'" % (exc.msg, cmd)
124
+ elif isinstance(exc, ExecutorException):
125
+ return "%s%r" % (exc.__class__.__name__, exc.args) # исключить многословный auxiliary
126
+ else:
127
+ return repr(exc)
128
+
129
+
130
+ def _print_hostname(host, style=None):
131
+ if style:
132
+ host = style + host + colorama.Style.RESET_ALL
133
+ print(" %s" % host)
134
+
135
+
136
+ def _print_limit(items, printer, limit, total, end="\n"):
137
+ if not items:
138
+ return
139
+ if len(items) > limit and len(items) > total * 0.7:
140
+ print(" ... %d hosts" % len(items))
141
+ for host in items[:limit]:
142
+ printer(host)
143
+ if len(items) > limit:
144
+ print(" ... %d more hosts" % (len(items) - limit))
145
+
146
+ print(end, end="")
147
+
148
+
149
+ def _print_failed(host, res):
150
+ exc = res[host]
151
+ color = colorama.Fore.YELLOW if isinstance(exc, Warning) else colorama.Fore.RED
152
+ print(" %s - %s" % (color + host + colorama.Style.RESET_ALL, _format_exc(exc)))
153
+
154
+
155
+ class DeferredFileWrite:
156
+ def __init__(self, file, mode="r"):
157
+ self._file = file
158
+ wrapper = {"w": "a", "wb": "ab"}
159
+ if mode in wrapper:
160
+ self._mode = wrapper[mode]
161
+ else:
162
+ raise Exception()
163
+
164
+ def write(self, data):
165
+ with open(self._file, self._mode) as fh:
166
+ fh.write(data)
167
+
168
+ def close(self):
169
+ pass
170
+
171
+ def flush(self):
172
+ pass