netstack 0.1.1 → 0.2.0

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.
Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -3
  3. data/{LICENSE.txt → LICENSE} +7 -6
  4. data/NOTICE +25 -0
  5. data/README.md +58 -20
  6. data/docs/acceptance.md +92 -0
  7. data/docs/backups.md +100 -0
  8. data/docs/configuration.md +198 -0
  9. data/docs/connector.md +187 -0
  10. data/docs/ddi.md +142 -0
  11. data/docs/deployment.md +47 -0
  12. data/docs/foundations.md +45 -0
  13. data/docs/implementation.md +68 -0
  14. data/docs/inventory.md +42 -0
  15. data/docs/monitoring.md +227 -0
  16. data/docs/motor_integration.md +96 -0
  17. data/docs/performance.md +39 -0
  18. data/docs/release.md +20 -0
  19. data/docs/vxlan.md +91 -0
  20. data/docs/wireless.md +81 -0
  21. data/lib/netstack/backups/artifact.rb +23 -0
  22. data/lib/netstack/backups/diff.rb +15 -0
  23. data/lib/netstack/backups/repository.rb +77 -0
  24. data/lib/netstack/backups/request.rb +78 -0
  25. data/lib/netstack/circuits/business_calendar.rb +51 -0
  26. data/lib/netstack/circuits/capacity_review.rb +191 -0
  27. data/lib/netstack/circuits/endpoint.rb +25 -0
  28. data/lib/netstack/circuits/monitoring_plan.rb +212 -0
  29. data/lib/netstack/configuration/artifact.rb +85 -0
  30. data/lib/netstack/configuration/artifact_resolver.rb +30 -0
  31. data/lib/netstack/configuration/bootstrap/payload.rb +67 -0
  32. data/lib/netstack/configuration/bootstrap/validator.rb +224 -0
  33. data/lib/netstack/configuration/catalog.rb +60 -0
  34. data/lib/netstack/configuration/generator.rb +87 -0
  35. data/lib/netstack/configuration/input_contract.rb +82 -0
  36. data/lib/netstack/configuration/payload.rb +168 -0
  37. data/lib/netstack/configuration/reader.rb +124 -0
  38. data/lib/netstack/configuration/support.rb +30 -0
  39. data/lib/netstack/configuration/system_configuration.rb +56 -0
  40. data/lib/netstack/configuration/template_manager.rb +174 -0
  41. data/lib/netstack/configuration/template_store.rb +40 -0
  42. data/lib/netstack/configuration/validator.rb +566 -0
  43. data/lib/netstack/connector/authentication.rb +77 -0
  44. data/lib/netstack/connector/authentication_error.rb +11 -0
  45. data/lib/netstack/connector/backup.rb +19 -0
  46. data/lib/netstack/connector/backup_busy.rb +11 -0
  47. data/lib/netstack/connector/backup_persistence_error.rb +32 -0
  48. data/lib/netstack/connector/command.rb +63 -0
  49. data/lib/netstack/connector/command_result.rb +24 -0
  50. data/lib/netstack/connector/command_timeout.rb +11 -0
  51. data/lib/netstack/connector/configuration.rb +138 -0
  52. data/lib/netstack/connector/connection_closed.rb +11 -0
  53. data/lib/netstack/connector/connection_error.rb +11 -0
  54. data/lib/netstack/connector/device.rb +284 -0
  55. data/lib/netstack/connector/device_error.rb +11 -0
  56. data/lib/netstack/connector/dialogue.rb +55 -0
  57. data/lib/netstack/connector/error.rb +44 -0
  58. data/lib/netstack/connector/error_metadata.rb +58 -0
  59. data/lib/netstack/connector/event.rb +24 -0
  60. data/lib/netstack/connector/execution.rb +110 -0
  61. data/lib/netstack/connector/interaction.rb +55 -0
  62. data/lib/netstack/connector/internal_error.rb +11 -0
  63. data/lib/netstack/connector/known_hosts.rb +128 -0
  64. data/lib/netstack/connector/local_backup.rb +63 -0
  65. data/lib/netstack/connector/log/event.rb +65 -0
  66. data/lib/netstack/connector/log/formatter.rb +18 -0
  67. data/lib/netstack/connector/log/messages.rb +54 -0
  68. data/lib/netstack/connector/log/redacting_writer.rb +33 -0
  69. data/lib/netstack/connector/log/transcript.rb +35 -0
  70. data/lib/netstack/connector/log.rb +262 -0
  71. data/lib/netstack/connector/log_error.rb +11 -0
  72. data/lib/netstack/connector/login_timeout.rb +11 -0
  73. data/lib/netstack/connector/output_limit_exceeded.rb +11 -0
  74. data/lib/netstack/connector/parsing_error.rb +11 -0
  75. data/lib/netstack/connector/profile/builder.rb +238 -0
  76. data/lib/netstack/connector/profile.rb +225 -0
  77. data/lib/netstack/connector/prompt_error.rb +11 -0
  78. data/lib/netstack/connector/recovery.rb +59 -0
  79. data/lib/netstack/connector/redactor.rb +100 -0
  80. data/lib/netstack/connector/response.rb +25 -0
  81. data/lib/netstack/connector/response_reader.rb +140 -0
  82. data/lib/netstack/connector/result.rb +50 -0
  83. data/lib/netstack/connector/running_config/rendered.rb +16 -0
  84. data/lib/netstack/connector/running_config/strategy.rb +28 -0
  85. data/lib/netstack/connector/running_config.rb +113 -0
  86. data/lib/netstack/connector/save_config.rb +23 -0
  87. data/lib/netstack/connector/script.rb +59 -0
  88. data/lib/netstack/connector/script_error.rb +11 -0
  89. data/lib/netstack/connector/script_output_limit_exceeded.rb +11 -0
  90. data/lib/netstack/connector/session.rb +414 -0
  91. data/lib/netstack/connector/session_busy.rb +11 -0
  92. data/lib/netstack/connector/storage/backup_lock.rb +117 -0
  93. data/lib/netstack/connector/storage/private_file.rb +111 -0
  94. data/lib/netstack/connector/storage/safe_file.rb +64 -0
  95. data/lib/netstack/connector/storage/saved_config.rb +76 -0
  96. data/lib/netstack/connector/terminal_renderer.rb +186 -0
  97. data/lib/netstack/connector/terminal_text.rb +32 -0
  98. data/lib/netstack/connector/textfsm.rb +71 -0
  99. data/lib/netstack/connector/tftp/file_upload.rb +53 -0
  100. data/lib/netstack/connector/tftp/strategy.rb +67 -0
  101. data/lib/netstack/connector/tftp.rb +116 -0
  102. data/lib/netstack/connector/tftp_completion_error.rb +36 -0
  103. data/lib/netstack/connector/tftp_receipt.rb +54 -0
  104. data/lib/netstack/connector/tftp_target.rb +65 -0
  105. data/lib/netstack/connector/topology/checkpoint_error.rb +16 -0
  106. data/lib/netstack/connector/topology/deployment_adapter.rb +163 -0
  107. data/lib/netstack/connector/topology/execution_result.rb +34 -0
  108. data/lib/netstack/connector/topology/immediate_strategy.rb +39 -0
  109. data/lib/netstack/connector/topology/interface_description.rb +46 -0
  110. data/lib/netstack/connector/topology/interface_name.rb +60 -0
  111. data/lib/netstack/connector/topology/plan.rb +105 -0
  112. data/lib/netstack/connector/topology/strategy.rb +76 -0
  113. data/lib/netstack/connector/topology.rb +255 -0
  114. data/lib/netstack/connector/transport_error.rb +11 -0
  115. data/lib/netstack/connector/transports.rb +201 -0
  116. data/lib/netstack/connector/underlying_error.rb +25 -0
  117. data/lib/netstack/connector/unsupported_operation.rb +11 -0
  118. data/lib/netstack/connector/vendors/cisco_ios/running_config.rb +22 -0
  119. data/lib/netstack/connector/vendors/cisco_ios/tftp_backup.rb +41 -0
  120. data/lib/netstack/connector/vendors/cisco_ios/topology.rb +54 -0
  121. data/lib/netstack/connector/vendors/cisco_ios.rb +44 -0
  122. data/lib/netstack/connector/vendors/cisco_nxos/running_config.rb +25 -0
  123. data/lib/netstack/connector/vendors/cisco_nxos/tftp_backup.rb +43 -0
  124. data/lib/netstack/connector/vendors/cisco_nxos.rb +51 -0
  125. data/lib/netstack/connector/vendors/h3c/tftp_backup.rb +34 -0
  126. data/lib/netstack/connector/vendors/h3c/topology.rb +72 -0
  127. data/lib/netstack/connector/vendors/h3c.rb +75 -0
  128. data/lib/netstack/connector/vendors/h3c_wireless.rb +13 -0
  129. data/lib/netstack/connector/vendors/hillstone/running_config.rb +16 -0
  130. data/lib/netstack/connector/vendors/hillstone/tftp_backup.rb +55 -0
  131. data/lib/netstack/connector/vendors/hillstone/topology.rb +64 -0
  132. data/lib/netstack/connector/vendors/hillstone.rb +39 -0
  133. data/lib/netstack/connector/vendors/huawei/tftp_backup.rb +26 -0
  134. data/lib/netstack/connector/vendors/huawei.rb +62 -0
  135. data/lib/netstack/connector/vendors/palo_alto/running_config.rb +77 -0
  136. data/lib/netstack/connector/vendors/palo_alto/tftp_backup.rb +49 -0
  137. data/lib/netstack/connector/vendors/palo_alto/topology.rb +77 -0
  138. data/lib/netstack/connector/vendors/palo_alto.rb +39 -0
  139. data/lib/netstack/connector/vendors/radware/running_config.rb +21 -0
  140. data/lib/netstack/connector/vendors/radware/tftp_backup.rb +49 -0
  141. data/lib/netstack/connector/vendors/radware/topology.rb +19 -0
  142. data/lib/netstack/connector/vendors/radware.rb +59 -0
  143. data/lib/netstack/connector/write_timeout.rb +11 -0
  144. data/lib/netstack/connector.rb +32 -0
  145. data/lib/netstack/ddi/applier.rb +107 -0
  146. data/lib/netstack/ddi/deployment.rb +137 -0
  147. data/lib/netstack/ddi/inputs.rb +61 -0
  148. data/lib/netstack/ddi/manager.rb +197 -0
  149. data/lib/netstack/ddi/plan.rb +99 -0
  150. data/lib/netstack/ddi/scope.rb +127 -0
  151. data/lib/netstack/ddi/settings.rb +44 -0
  152. data/lib/netstack/ddi/state.rb +166 -0
  153. data/lib/netstack/deployment/checkpoint_error.rb +14 -0
  154. data/lib/netstack/deployment/cli_adapter.rb +64 -0
  155. data/lib/netstack/deployment/executor.rb +131 -0
  156. data/lib/netstack/deployment/plan.rb +82 -0
  157. data/lib/netstack/deployment/receipt.rb +78 -0
  158. data/lib/netstack/deployment/recovery.rb +19 -0
  159. data/lib/netstack/deployment/step.rb +16 -0
  160. data/lib/netstack/dimension.rb +30 -0
  161. data/lib/netstack/error.rb +14 -0
  162. data/lib/netstack/input_error.rb +9 -0
  163. data/lib/netstack/integrations/bluecat/client.rb +84 -0
  164. data/lib/netstack/integrations/http/client.rb +146 -0
  165. data/lib/netstack/integrations/http/error.rb +10 -0
  166. data/lib/netstack/integrations/http/response.rb +28 -0
  167. data/lib/netstack/integrations/http_delivery/adapter.rb +51 -0
  168. data/lib/netstack/integrations/http_delivery/client.rb +127 -0
  169. data/lib/netstack/integrations/http_delivery/result.rb +27 -0
  170. data/lib/netstack/integrations/oxidized/client.rb +89 -0
  171. data/lib/netstack/integrations/zabbix/client.rb +211 -0
  172. data/lib/netstack/integrations/zabbix/error.rb +10 -0
  173. data/lib/netstack/inventory/address.rb +30 -0
  174. data/lib/netstack/inventory/changes.rb +13 -0
  175. data/lib/netstack/inventory/cli_parser.rb +75 -0
  176. data/lib/netstack/inventory/collector.rb +99 -0
  177. data/lib/netstack/inventory/device.rb +14 -0
  178. data/lib/netstack/inventory/interface.rb +25 -0
  179. data/lib/netstack/inventory/neighbor.rb +15 -0
  180. data/lib/netstack/inventory/reconciler.rb +74 -0
  181. data/lib/netstack/inventory/snapshot.rb +69 -0
  182. data/lib/netstack/inventory/transceiver.rb +23 -0
  183. data/lib/netstack/ipv4.rb +204 -0
  184. data/lib/netstack/ipv6.rb +88 -0
  185. data/lib/netstack/monitoring/applier.rb +179 -0
  186. data/lib/netstack/monitoring/device_plan.rb +23 -0
  187. data/lib/netstack/monitoring/host_plan.rb +152 -0
  188. data/lib/netstack/monitoring/inputs.rb +73 -0
  189. data/lib/netstack/monitoring/measurements.rb +89 -0
  190. data/lib/netstack/monitoring/mutation.rb +37 -0
  191. data/lib/netstack/monitoring/plan.rb +69 -0
  192. data/lib/netstack/monitoring/probe_plan.rb +19 -0
  193. data/lib/netstack/monitoring/state_comparison.rb +37 -0
  194. data/lib/netstack/range_set.rb +181 -0
  195. data/lib/netstack/routing/lookup.rb +46 -0
  196. data/lib/netstack/routing/lookup_result.rb +14 -0
  197. data/lib/netstack/routing/route.rb +32 -0
  198. data/lib/netstack/routing/snapshot.rb +23 -0
  199. data/lib/netstack/target.rb +15 -0
  200. data/lib/netstack/unsupported_capability.rb +9 -0
  201. data/lib/netstack/values.rb +70 -0
  202. data/lib/netstack/version.rb +1 -1
  203. data/lib/netstack/vxlan/auditor.rb +187 -0
  204. data/lib/netstack/vxlan/cisco_nxos/config_parser.rb +327 -0
  205. data/lib/netstack/vxlan/cisco_nxos/deployment_adapter.rb +63 -0
  206. data/lib/netstack/vxlan/cisco_nxos/runtime_parser.rb +182 -0
  207. data/lib/netstack/vxlan/cisco_nxos/segment_renderer.rb +42 -0
  208. data/lib/netstack/vxlan/collector.rb +77 -0
  209. data/lib/netstack/vxlan/fabric.rb +29 -0
  210. data/lib/netstack/vxlan/finding.rb +20 -0
  211. data/lib/netstack/vxlan/node.rb +51 -0
  212. data/lib/netstack/vxlan/route_policy.rb +57 -0
  213. data/lib/netstack/vxlan/runtime_result.rb +24 -0
  214. data/lib/netstack/vxlan/segment.rb +35 -0
  215. data/lib/netstack/vxlan/segment_plan.rb +25 -0
  216. data/lib/netstack/vxlan/segment_planner.rb +156 -0
  217. data/lib/netstack/vxlan/snapshot.rb +34 -0
  218. data/lib/netstack/vxlan/validation.rb +58 -0
  219. data/lib/netstack/vxlan/verifier.rb +52 -0
  220. data/lib/netstack/vxlan/vpc_domain.rb +20 -0
  221. data/lib/netstack/wireless/access_point.rb +12 -0
  222. data/lib/netstack/wireless/ap_group.rb +12 -0
  223. data/lib/netstack/wireless/assessment.rb +13 -0
  224. data/lib/netstack/wireless/assessor.rb +83 -0
  225. data/lib/netstack/wireless/authentication_connection.rb +12 -0
  226. data/lib/netstack/wireless/bss.rb +12 -0
  227. data/lib/netstack/wireless/client.rb +12 -0
  228. data/lib/netstack/wireless/collector.rb +191 -0
  229. data/lib/netstack/wireless/configuration.rb +13 -0
  230. data/lib/netstack/wireless/finding.rb +12 -0
  231. data/lib/netstack/wireless/h3c/config_parser.rb +229 -0
  232. data/lib/netstack/wireless/h3c/lldp_parser.rb +69 -0
  233. data/lib/netstack/wireless/h3c/runtime_parser.rb +240 -0
  234. data/lib/netstack/wireless/huawei/runtime_parser.rb +83 -0
  235. data/lib/netstack/wireless/invalid_snapshot.rb +11 -0
  236. data/lib/netstack/wireless/neighbor.rb +12 -0
  237. data/lib/netstack/wireless/normalizer.rb +90 -0
  238. data/lib/netstack/wireless/parse_result.rb +13 -0
  239. data/lib/netstack/wireless/radio.rb +12 -0
  240. data/lib/netstack/wireless/reconciler.rb +115 -0
  241. data/lib/netstack/wireless/reconciliation.rb +11 -0
  242. data/lib/netstack/wireless/security_policy.rb +12 -0
  243. data/lib/netstack/wireless/service_template.rb +12 -0
  244. data/lib/netstack/wireless/service_template_bindings.rb +56 -0
  245. data/lib/netstack/wireless/snapshot.rb +150 -0
  246. data/lib/netstack/wireless/state.rb +23 -0
  247. data/lib/netstack/wireless/view.rb +154 -0
  248. data/lib/netstack.rb +10 -3
  249. data/resources/configuration/cisco-bootstrap.erb +118 -0
  250. data/resources/configuration/cisco-distributed-gateway.erb +40 -0
  251. data/resources/configuration/cisco-n9k-vpc.erb +62 -0
  252. data/resources/configuration/cisco-server-port.erb +43 -0
  253. data/resources/configuration/h3c-bootstrap.erb +108 -0
  254. data/resources/configuration/h3c-irf.erb +57 -0
  255. data/resources/configuration/h3c-server-port.erb +36 -0
  256. data/resources/configuration/hillstone-ipsec-vpn.erb +68 -0
  257. data/resources/configuration/hillstone-policy.erb +31 -0
  258. data/resources/configuration/paloalto-ipsec-vpn.erb +39 -0
  259. data/resources/configuration/radware-load-balancer.erb +26 -0
  260. data/resources/textfsm/cisco_cdp_neighbors_detail.textfsm +7 -0
  261. data/resources/textfsm/cisco_ios_running_config_interfaces.textfsm +12 -0
  262. data/resources/textfsm/cisco_ios_show_ip_interface_brief.textfsm +7 -0
  263. data/resources/textfsm/h3c_interface_descriptions.textfsm +11 -0
  264. data/resources/textfsm/h3c_lldp_local_first.textfsm +8 -0
  265. data/resources/textfsm/h3c_lldp_name_first.textfsm +8 -0
  266. data/resources/textfsm/hillstone_interface_descriptions.textfsm +11 -0
  267. data/resources/textfsm/hillstone_lldp_neighbors.textfsm +7 -0
  268. data/resources/textfsm/index +8 -0
  269. data/resources/textfsm/palo_alto_interface_descriptions.textfsm +6 -0
  270. data/resources/textfsm/palo_alto_lldp_neighbors.textfsm +11 -0
  271. data/resources/textfsm/radware_port_names.textfsm +13 -0
  272. metadata +588 -27
  273. data/.rubocop.yml +0 -13
  274. data/CODE_OF_CONDUCT.md +0 -84
  275. data/Gemfile +0 -10
  276. data/Gemfile.lock +0 -40
  277. data/Rakefile +0 -8
  278. data/bin/console +0 -15
  279. data/bin/setup +0 -8
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class H3c < Netstack::Connector::Device
9
+ vendor :h3c
10
+ # 声明 H3C 的命令、提示、诊断和保存确认规则。
11
+ profile do
12
+ running_config_strategy Netstack::Connector::RunningConfig::Rendered
13
+ tftp_strategy Netstack::Connector::Vendors::H3c::TftpBackup
14
+ topology_strategy Netstack::Connector::Vendors::H3c::Topology
15
+ commands do
16
+ running_config "dis cur"
17
+ save_config "save force"
18
+ end
19
+
20
+ prompts do
21
+ login(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*\S+[>\]](?:[^\S\n]|\x00)*\z/)
22
+ command(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*[^\r\n]+[>\]](?:[^\S\n]|\x00)*\z/)
23
+ username(/(?:login|Username):\s*\z/i)
24
+ end
25
+
26
+ errors do
27
+ command(
28
+ /
29
+ ^[ \t]*%[ \t]+(?:(?:Unrecognized|Ambiguous|Incomplete)\s+command|Too\s+many\s+parameters)
30
+ \s+found\s+at\s+'\^'\s+position\.
31
+ /ix,
32
+ /^[ \t]*error:/i,
33
+ /^[ \t]*Permission\s+denied\./i,
34
+ /^\s*\^/
35
+ )
36
+ end
37
+
38
+ # 核心交换机配置采集实测超过 10 秒,保留有界的整条命令预算。
39
+ command_timeout 60
40
+
41
+ interactions do
42
+ confirm %r{Are\s+you\s+sure\?\s*\[Y/N\]}i, response: "y\n"
43
+ confirm(/press\s+the\s+enter\s+key\)/i, response: "\n")
44
+ confirm %r{Continue\?\s*\[Y/N\]}i, response: "y\n"
45
+ confirm(/Please\s+input\s+the\s+file\s+name\(\*\.cfg\)\[[^\]]+\]\s*\z/i, response: "\n")
46
+ confirm %r{overwrite\?\s*\[Y/N\]}i, response: "y\n"
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ # 发送规则追加命令前,用本批次已获取的真实规则编号替换 XXX 占位符。
53
+ def prepare_command(command, execution)
54
+ return command unless /\A[ \t]*rule[ \t]+XXX[ \t]+append\b/i.match?(command.text)
55
+
56
+ rule_id = execution.context[:rule_id]
57
+ raise execution.build_error("new rule ID has not been acquired") unless rule_id
58
+
59
+ command.with_text(command.text.sub(/XXX/i, rule_id))
60
+ end
61
+
62
+ # 规则创建成功后查询当前配置,提取设备分配的编号并保存到本批次变量。
63
+ def after_command(command, _response, execution)
64
+ return unless /\A[ \t]*rule[ \t]+pass\b/i.match?(command.text)
65
+
66
+ response = execution.execute_command("dis this")
67
+ rule_id = response.raw[/rule\s+(\d+)\s+pass[^\n]+\n\s*#/i, 1]
68
+ raise execution.build_error("device did not return the new rule ID") unless rule_id
69
+
70
+ execution.context[:rule_id] = rule_id
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class H3cWireless < H3c
9
+ vendor :h3c_wireless
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Hillstone
9
+ class RunningConfig < Netstack::Connector::RunningConfig::Strategy
10
+ # 去掉山石 CLI 用退格控制符绘制的分页痕迹。
11
+ def clean(text) = text.gsub(/\x00?\x08+[ \t]+\x08+/, "")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Hillstone
9
+ class TftpBackup < Tftp::Strategy
10
+ # 山石原生导出的配置文件使用 dat 扩展名。
11
+ def self.file_extension = "dat"
12
+
13
+ # 在连接前拒绝源文件及远端目录;未指定文件名时仍由设备生成。
14
+ def validate_options!(target, source_file:, **)
15
+ raise ArgumentError, "Hillstone TFTP export does not use source_file" unless source_file.nil?
16
+ return unless target.explicit_path? && target.path.include?("/")
17
+
18
+ raise ArgumentError, "Hillstone TFTP filename cannot contain a directory"
19
+ end
20
+
21
+ def receipt_metadata(target, **)
22
+ { configuration_kind: :startup, format: :dat, requested_path: target.explicit_path? ? target.path : nil }
23
+ end
24
+
25
+ # 构造当前厂商的 TFTP 导出交互脚本。
26
+ def script(target, source_file:, vrf: nil)
27
+ validate_options!(target, source_file: source_file, vrf: vrf)
28
+
29
+ command = "export configuration startup to tftp server #{target.host} vrouter #{vrf || "mgt-vr"}"
30
+ command += " #{target.path}" if target.explicit_path?
31
+ Script.new([Command.new(command, timeout: 180)])
32
+ end
33
+
34
+ # 检查设备回显是否确认 TFTP 传输完成。
35
+ def device_reported_complete?(result)
36
+ !completion_line(result).nil?
37
+ end
38
+
39
+ # 从山石成功回显提取实际目标文件名。
40
+ def remote_path(_target, result)
41
+ filename = completion_line(result)&.split&.last
42
+ TftpTarget.validate_path!(filename)
43
+ end
44
+
45
+ private
46
+
47
+ # 仅认可设备明确给出目标文件名的 Export ok 回显。
48
+ def completion_line(result)
49
+ completion_lines(result).find { |line| line.match?(/\AExport\s+ok\s*,\s*target\s+file\s+name\s+\S+\z/i) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Hillstone
9
+ class Topology < Netstack::Connector::Topology::ImmediateStrategy
10
+ # 山石提供 LLDP 邻居、配置描述读取和描述变更。
11
+ def self.supports?(capability)
12
+ %i[neighbors interface_descriptions interface_description_changes].include?(capability)
13
+ end
14
+
15
+ # 读取 LLDP 邻居信息。
16
+ def neighbor_command = "show lldp neighbor-information"
17
+
18
+ # 从运行配置解析接口描述。
19
+ def description_template = "hillstone_interface_descriptions.textfsm"
20
+
21
+ # 进入配置视图。
22
+ def enter_configuration_command = "configure"
23
+
24
+ # 退出配置视图后先读回,保存动作随后单独执行。
25
+ def leave_configuration_commands = ["exit"]
26
+
27
+ def persistence_pattern = /\ASaving configuration is finished\.?\z/i
28
+
29
+ # 用数据行和设备报告的总数交叉核对,避免遗漏邻居。
30
+ def expected_neighbor_count(output, _template)
31
+ reported = output[/Total lldp neighbor number:\s*(\d+)/i, 1]
32
+ lines = neighbor_lines(output)
33
+ return unless lines.all? { |line| line.match?(/\A\S+\s+ethernet\d+\/\d+\s/i) }
34
+ return if reported && reported.to_i != lines.size
35
+
36
+ lines.size
37
+ end
38
+
39
+ # 仅在列表为空且有明确零邻居证据时认可空表。
40
+ def empty_neighbor_output?(output)
41
+ neighbor_lines(output).empty? && (output.match?(/Total lldp neighbor number:\s*0\b/i) ||
42
+ output.match?(/System Name\s+Local Interface/i))
43
+ end
44
+
45
+ # 复用公共接口描述命令。
46
+ def change_commands(change)
47
+ Netstack::Connector::Topology::InterfaceDescription.commands(interface: change.interface, description: change.new_description)
48
+ end
49
+
50
+ private
51
+
52
+ # 去掉命令、表头、总数和提示符,只保留邻居数据行。
53
+ def neighbor_lines(output)
54
+ output.lines.map(&:strip).reject do |line|
55
+ line.empty? || line == neighbor_command || line.match?(/\ASystem Name\s+Local Interface\b/i) ||
56
+ line.match?(/\ATotal lldp neighbor number:\s*\d+\.?\z/i) ||
57
+ line.match?(/\A[-=+ ]+\z/) || line.match?(/\A\S+(?:#|\([MBF]\))\z/)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Hillstone < Netstack::Connector::Device
9
+ vendor :hillstone
10
+ profile do
11
+ running_config_strategy Netstack::Connector::Vendors::Hillstone::RunningConfig
12
+ tftp_strategy Netstack::Connector::Vendors::Hillstone::TftpBackup
13
+ topology_strategy Netstack::Connector::Vendors::Hillstone::Topology
14
+ commands do
15
+ running_config "terminal length 0", "show configuration running"
16
+ save_config "save all"
17
+ end
18
+
19
+ prompts do
20
+ login(/(?:\A|(?<=[\r\n]))\S+(?:[#>$]|\([MBF]\))\s*\z/)
21
+ command(/(?:\A|(?<=[\r\n]))\S+(?:#|\([MBF]\))\s*\z/)
22
+ end
23
+
24
+ pager do
25
+ pattern(/(?:\A|(?<=[\r\n]))[ \t]*--More--[ \t\x00\x08]*(?=\r|\n|\z)/i)
26
+ end
27
+
28
+ errors do
29
+ command(/^[ \t]*\^-+/i, /^\s*\^/,
30
+ /^[ \t]*(?:Unknown|Invalid|Error)\s+command/i,
31
+ /^[ \t]*Error:\s*[^\r\n]+/i)
32
+ end
33
+
34
+ command_timeout 60
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Huawei
9
+ class TftpBackup < Tftp::FileUpload
10
+ # 缺少源文件也必须在首次设备 I/O 之前拒绝。
11
+ def validate_options!(target, source_file:, vrf: nil)
12
+ super
13
+ raise ArgumentError, "Huawei TFTP backup requires source_file" if source_file.nil?
14
+ end
15
+
16
+ # 要求调用方提供合法的华为启动配置文件路径。
17
+ def resolve_source_file(value)
18
+ raise ArgumentError, "Huawei TFTP backup requires source_file" unless value
19
+
20
+ TftpTarget.validate_source_file!(value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Huawei < Netstack::Connector::Device
9
+ vendor :huawei
10
+ # 声明 Huawei 的命令、提示、诊断、提权和保存确认规则。
11
+ profile do
12
+ running_config_strategy Netstack::Connector::RunningConfig::Rendered
13
+ tftp_strategy Netstack::Connector::Vendors::Huawei::TftpBackup
14
+ commands do
15
+ running_config "dis cur"
16
+ save_config "save force"
17
+ end
18
+
19
+ prompts do
20
+ login(/(?:\A|(?<=[\r\n]))\s*\S+[>\]]\s*\z/)
21
+ command(/(?:\A|(?<=[\r\n]))[^\r\n]+[>\]]\s*\z/)
22
+ username(/(?:login|Username):\s*\z/i)
23
+ end
24
+
25
+ errors do
26
+ command(
27
+ /^\s*\^/,
28
+ /^[ \t]*error:/i,
29
+ /^[ \t]*Permission\s+denied\./i,
30
+ /
31
+ ^[ \t]*%[ \t]+(?:(?:Unrecognized|Ambiguous|Incomplete)\s+command|Too\s+many\s+parameters)
32
+ \s+found\s+at\s+'\^'\s+position\.
33
+ /ix
34
+ )
35
+ end
36
+
37
+ privilege do
38
+ command "su"
39
+ prompt(/privilege\s+(?:level\s+is|is\s+).+>\s*\z/im)
40
+ end
41
+
42
+ command_timeout 10
43
+
44
+ interactions do
45
+ confirm %r{Are\s+you\s+sure\?\s*\[Y/N\]}i, response: "y\n"
46
+ confirm(/press\s+the\s+enter\s+key\)/i, response: "\n")
47
+ confirm %r{Continue\?\s*\[Y/N\]}i, response: "y\n"
48
+ confirm(/Please\s+input\s+the\s+file\s+name\(\*\.cfg\)\[[^\]]+\]\s*\z/i, response: "\n")
49
+ confirm %r{overwrite\?\s*\[Y/N\]}i, response: "y\n"
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ # 登录后若提示符以 ] 结尾,则确认设备已经处于特权视图。
56
+ def after_login(session, response)
57
+ session.mark_privileged! if response.prompt.include?("]")
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ require "shellwords"
6
+
7
+ module Netstack
8
+ module Connector
9
+ module Vendors
10
+ class PaloAlto
11
+ class RunningConfig < Netstack::Connector::RunningConfig::Strategy
12
+ # 配置位于 show 步骤,最后的 exit 只负责离开候选视图。
13
+ def result_step(result)
14
+ result.steps.find { |step| step.command.text == "show" }
15
+ end
16
+
17
+ # 采集切换视图时只改变结束符,仍绑定已认证的设备身份。
18
+ def prompt_text(command)
19
+ prompt = super
20
+ case command.text
21
+ when "configure" then prompt.sub(/[>#]\z/, "#")
22
+ when "exit" then prompt.sub(/[>#]\z/, ">")
23
+ else prompt
24
+ end
25
+ end
26
+
27
+ # 采集前后都检查候选差异,拒绝把未提交配置误作运行配置。
28
+ def validate_response!(command, response, _execution)
29
+ return unless command.text == "show config diff"
30
+ return if body(response.output, command.text).empty?
31
+
32
+ raise DeviceError.new("PAN-OS candidate differs from running configuration",
33
+ code: :uncommitted_configuration, host: @device.host, phase: :collect)
34
+ end
35
+
36
+ # 只接受完整的 set 命令文本,排除 XML 或截断的引号配置。
37
+ def clean(text)
38
+ normalized = body(text, "show").sub(/(?:\A|\n)[ \t]*\[edit\][ \t]*\z/, "").strip
39
+ unless set_commands?(normalized)
40
+ raise DeviceError.new("PAN-OS configuration must be set format",
41
+ code: :unsupported_configuration_format, host: @device.host, phase: :collect)
42
+ end
43
+ normalized
44
+ end
45
+
46
+ private
47
+
48
+ # 证书等引号内换行必须闭合,不能将截断文本当成配置。
49
+ def set_commands?(text)
50
+ return false if text.empty?
51
+
52
+ pending = +""
53
+ text.each_line do |line|
54
+ next if pending.empty? && line.strip.empty?
55
+ return false if pending.empty? && !line.start_with?("set ")
56
+
57
+ pending << line
58
+ begin
59
+ Shellwords.split(pending)
60
+ pending.clear
61
+ rescue ArgumentError
62
+ # 未闭合引号继续读取;末尾仍未闭合则拒绝整个采集结果。
63
+ end
64
+ end
65
+ pending.empty?
66
+ end
67
+
68
+ # 去掉命令回显和提示符,保留真正需要校验的设备响应。
69
+ def body(text, command)
70
+ text.gsub("\r\n", "\n").sub(/\A[ \t]*#{Regexp.escape(command)}[ \t]*\n/, "")
71
+ .sub(@device.profile.command_prompt, "").strip
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class PaloAlto
9
+ class TftpBackup < Tftp::Strategy
10
+ FILENAME = "running-config.xml"
11
+
12
+ # 返回 PAN-OS 固定使用的运行配置文件名。
13
+ def self.filename(_host, **) = FILENAME
14
+
15
+ # 默认文件名尚未生成时只校验显式目标;固定名冲突仍由批量计划拒绝。
16
+ def validate_options!(target, source_file:, vrf: nil)
17
+ raise ArgumentError, "PAN-OS does not use source_file" unless source_file.nil?
18
+ raise ArgumentError, "PAN-OS TFTP backup does not use vrf" unless vrf.nil?
19
+ return unless target.explicit_path? && target.path != FILENAME
20
+
21
+ raise ArgumentError, "PAN-OS TFTP export uses the fixed filename #{FILENAME}"
22
+ end
23
+
24
+ def receipt_metadata(_target, **)
25
+ { configuration_kind: :running, format: :xml, source_file: FILENAME }
26
+ end
27
+
28
+ # 构造当前厂商的 TFTP 导出交互脚本。
29
+ def script(target, source_file:, vrf: nil)
30
+ validate_options!(target, source_file: source_file, vrf: vrf)
31
+
32
+ Script.new([Command.new("tftp export configuration to #{target.host} from #{FILENAME}",
33
+ timeout: 180)])
34
+ end
35
+
36
+ # 检查设备回显是否确认 TFTP 传输完成。
37
+ def device_reported_complete?(result)
38
+ completion_lines(result).any? do |line|
39
+ line.match?(/\ASent\s+[1-9]\d*\s+bytes(?:\s+in\s+[\d.]+\s+(?:secs?|seconds?))?[.!]?\z/i)
40
+ end
41
+ end
42
+
43
+ # 返回 PAN-OS 实际上传的固定文件名。
44
+ def remote_path(_target, _result) = FILENAME
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class PaloAlto
9
+ class Topology < Netstack::Connector::Topology::Strategy
10
+ # 候选配置缺少经固件实验确认的所有权/commit 协议,暂只提供读取能力。
11
+ def self.supports?(capability)
12
+ %i[neighbors interface_descriptions].include?(capability)
13
+ end
14
+
15
+ def change_error_code = :candidate_isolation_unavailable
16
+
17
+ # 读取各本机接口的 LLDP 邻居详情。
18
+ def neighbor_command = "show lldp neighbors all"
19
+
20
+ # 从 set 格式配置解析接口备注。
21
+ def description_template = "palo_alto_interface_descriptions.textfsm"
22
+
23
+ # 进入候选配置视图。
24
+ def enter_configuration_command = "configure"
25
+
26
+ # 只计入非空邻居块,跳过没有对端的本机接口。
27
+ def expected_neighbor_count(output, _template)
28
+ neighbor_blocks(output).count { |block| !empty_neighbor_block?(block) }
29
+ end
30
+
31
+ # 输出必须明确说明无邻居,或每个本机接口块都明确为空。
32
+ def empty_neighbor_output?(output)
33
+ return true if output.match?(/No LLDP neighbors/i)
34
+
35
+ blocks = neighbor_blocks(output)
36
+ !blocks.empty? && blocks.all? { |block| empty_neighbor_block?(block) }
37
+ end
38
+
39
+ private
40
+
41
+ # PAN-OS 会列出启用 LLDP、但没有邻居的本机接口;只有明确为空的
42
+ # Neighbor information 块可跳过,缺字段的非空块继续交给数量校验拒绝。
43
+ def neighbor_blocks(output)
44
+ output.split(/^\s*Local information:\s*$/i).select { |block| block.match?(/^\s*Local interface:\s*\S+/i) }
45
+ end
46
+
47
+ # 邻居字段区只有空行和提示符时,才视为明确的空块。
48
+ def empty_neighbor_block?(block)
49
+ tail = block.split(/^\s*Neighbor information:\s*$/i, 2)
50
+ tail.size == 2 && tail.last.lines.all? { |line| line.strip.empty? || line.match?(/^\S+[>#]\s*$/) }
51
+ end
52
+
53
+ public
54
+
55
+ # 模板按行读取;未闭合的引号表示证据不完整,不能把首行当成旧描述。
56
+ def decode_description(value)
57
+ value = value.strip
58
+ value.start_with?('"') ? Shellwords.split(value).join(" ") : value
59
+ rescue ArgumentError
60
+ raise ParsingError.new("PAN-OS interface comment is not a complete single-line value",
61
+ code: :unrecognized_output, host: @device.host, phase: :parse)
62
+ end
63
+
64
+ # 用 PAN-OS set 语法生成接口备注变更。
65
+ def change_commands(change)
66
+ [%Q(set network interface ethernet #{change.interface} comment "#{change.new_description}")]
67
+ end
68
+
69
+ # commit 可能耗时较长,单独放宽该命令的超时。
70
+ def script_command(command)
71
+ command == "commit" ? Command.new(command, timeout: 300) : command
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class PaloAlto < Netstack::Connector::Device
9
+ vendor :palo_alto
10
+ # 声明 PAN-OS 的命令、提示、超时、诊断和登录确认规则。
11
+ profile do
12
+ running_config_strategy Netstack::Connector::Vendors::PaloAlto::RunningConfig
13
+ tftp_strategy Netstack::Connector::Vendors::PaloAlto::TftpBackup
14
+ topology_strategy Netstack::Connector::Vendors::PaloAlto::Topology
15
+ commands do
16
+ running_config "set cli pager off", "set cli config-output-format set", "show config diff",
17
+ "configure", "show", "exit", "show config diff"
18
+ end
19
+
20
+ prompts do
21
+ login(/(?:\A|(?<=[\r\n]))(?:[^\s@]+)@[^\s>#]+[>#]\s*\z/)
22
+ command(/(?:\A|(?<=[\r\n]))(?:[^\s@]+)@[^\s>#]+[>#]\s*\z/)
23
+ end
24
+
25
+ errors do
26
+ command(/^[ \t]*(?:Invalid syntax|Unknown command|Server error|Command failed)(?:[.:]|\s)/i)
27
+ end
28
+
29
+ command_timeout 120
30
+
31
+ interactions do
32
+ login %r{(?:acknowledge|accept)[\s\S]*(?:yes/no|\[y/n\]|enter\s+yes)[^\r\n]*\z}i,
33
+ response: "yes\n"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Radware
9
+ class RunningConfig < Netstack::Connector::RunningConfig::Rendered
10
+ # /cfg/dump 进入 Configuration 菜单;保留设备前缀,只替换菜单名称。
11
+ def prompt_text(command)
12
+ prompt = super
13
+ return prompt unless command.text == "/cfg/dump"
14
+
15
+ prompt.sub(/\A(>>[ \t]+(?:.* - )?)[^\r\n#]+#\z/, '\1Configuration#')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
4
+
5
+ module Netstack
6
+ module Connector
7
+ module Vendors
8
+ class Radware
9
+ class TftpBackup < Tftp::Strategy
10
+ # Radware 原生导出生成压缩配置归档。
11
+ def self.file_extension = "tgz"
12
+
13
+ # 在连接前拒绝当前厂商不支持的参数组合。
14
+ def validate_options!(_target, source_file:, vrf: nil)
15
+ raise ArgumentError, "Radware Alteon does not use source_file" unless source_file.nil?
16
+ raise ArgumentError, "Radware TFTP backup does not use vrf" unless vrf.nil?
17
+ end
18
+
19
+ def receipt_metadata(_target, **) = { configuration_kind: :native_archive, format: :tgz }
20
+
21
+ # 构造当前厂商的 TFTP 导出交互脚本。
22
+ def script(target, source_file:, vrf: nil)
23
+ validate_options!(target, source_file: source_file, vrf: vrf)
24
+
25
+ Script.new([Command.new("/cfg/ptcfg #{target.host} -tftp", timeout: 180, interactions: [
26
+ interaction(/Enter\s+(?:hostname|IP address)[^\r\n]*:\s*\z/i, "#{target.host}\n"),
27
+ interaction(/Enter\s+name\s+of\s+(?:\.tgz\s+)?file[^\r\n]*:\s*\z/i, "#{target.path}\n"),
28
+ interaction(/Enter\s+username[^\r\n]*:\s*\z/i, "\n"),
29
+ interaction(/Include\s+private\s+keys\?\s*\[y\/n\]:\s*\z/i, "n\n"),
30
+ interaction(/Enter\s+"?mansync"?\s+to\s+get\s+real\/group\/virt\s+internal\s+index\s+config:\s*\z/i,
31
+ "mansync\n")
32
+ ])])
33
+ end
34
+
35
+ # 检查设备回显是否确认 TFTP 传输完成。
36
+ def device_reported_complete?(result)
37
+ completion_lines(result).any? do |line|
38
+ line.match?(/\A
39
+ (?:Current\s+)?(?:configuration|config)\s+
40
+ (?:successfully\s+(?:uploaded|tftp'd)|(?:uploaded|tftp'd)(?:\s+successfully)?|
41
+ complete(?:d)?(?:\s+successfully)?)[.!]?
42
+ \z/ix)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end