net-connector 0.4.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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE +21 -0
  4. data/README.md +277 -0
  5. data/docs/RELEASING.md +81 -0
  6. data/docs/VERIFICATION.md +73 -0
  7. data/docs/architecture.md +268 -0
  8. data/exe/net-connector-backup +6 -0
  9. data/lib/net/connector/device/base.rb +368 -0
  10. data/lib/net/connector/device/interface_description.rb +41 -0
  11. data/lib/net/connector/device/interface_name.rb +56 -0
  12. data/lib/net/connector/device/profile.rb +446 -0
  13. data/lib/net/connector/device/running_config/strategy.rb +29 -0
  14. data/lib/net/connector/device/running_config.rb +69 -0
  15. data/lib/net/connector/engine/authentication.rb +77 -0
  16. data/lib/net/connector/engine/base.rb +4 -0
  17. data/lib/net/connector/engine/command.rb +101 -0
  18. data/lib/net/connector/engine/configuration.rb +131 -0
  19. data/lib/net/connector/engine/core.rb +6 -0
  20. data/lib/net/connector/engine/dialogue.rb +223 -0
  21. data/lib/net/connector/engine/errors.rb +183 -0
  22. data/lib/net/connector/engine/execution.rb +64 -0
  23. data/lib/net/connector/engine/log.rb +227 -0
  24. data/lib/net/connector/engine/log_messages.rb +47 -0
  25. data/lib/net/connector/engine/profile.rb +4 -0
  26. data/lib/net/connector/engine/recovery.rb +57 -0
  27. data/lib/net/connector/engine/result.rb +67 -0
  28. data/lib/net/connector/engine/session.rb +333 -0
  29. data/lib/net/connector/engine/terminal_renderer.rb +158 -0
  30. data/lib/net/connector/engine/transport.rb +184 -0
  31. data/lib/net/connector/engine.rb +4 -0
  32. data/lib/net/connector/netdisco/batch.rb +71 -0
  33. data/lib/net/connector/netdisco/cli.rb +142 -0
  34. data/lib/net/connector/netdisco/client.rb +148 -0
  35. data/lib/net/connector/netdisco/config_file.rb +87 -0
  36. data/lib/net/connector/netdisco/device.rb +75 -0
  37. data/lib/net/connector/netdisco/fleet.rb +258 -0
  38. data/lib/net/connector/netdisco/planner.rb +77 -0
  39. data/lib/net/connector/netdisco/result_store.rb +43 -0
  40. data/lib/net/connector/netdisco/rules.rb +131 -0
  41. data/lib/net/connector/netdisco/settings.rb +183 -0
  42. data/lib/net/connector/netdisco/worker.rb +75 -0
  43. data/lib/net/connector/netdisco.rb +14 -0
  44. data/lib/net/connector/operations/local_backup.rb +49 -0
  45. data/lib/net/connector/operations/parse_output.rb +60 -0
  46. data/lib/net/connector/operations/private_file.rb +25 -0
  47. data/lib/net/connector/operations/running_config/cisco.rb +14 -0
  48. data/lib/net/connector/operations/running_config/hillstone.rb +12 -0
  49. data/lib/net/connector/operations/running_config/palo_alto.rb +12 -0
  50. data/lib/net/connector/operations/running_config/strategy.rb +3 -0
  51. data/lib/net/connector/operations/running_config.rb +16 -0
  52. data/lib/net/connector/operations/saved_config.rb +72 -0
  53. data/lib/net/connector/operations/tftp/cisco_ios.rb +11 -0
  54. data/lib/net/connector/operations/tftp/cisco_nxos.rb +11 -0
  55. data/lib/net/connector/operations/tftp/h3c.rb +11 -0
  56. data/lib/net/connector/operations/tftp/hillstone.rb +11 -0
  57. data/lib/net/connector/operations/tftp/huawei.rb +11 -0
  58. data/lib/net/connector/operations/tftp/palo_alto.rb +11 -0
  59. data/lib/net/connector/operations/tftp/radware.rb +11 -0
  60. data/lib/net/connector/operations/tftp/strategy.rb +77 -0
  61. data/lib/net/connector/operations/tftp_backup.rb +120 -0
  62. data/lib/net/connector/operations/topology/cisco.rb +11 -0
  63. data/lib/net/connector/operations/topology/h3c.rb +11 -0
  64. data/lib/net/connector/operations/topology/hillstone.rb +11 -0
  65. data/lib/net/connector/operations/topology/palo_alto.rb +11 -0
  66. data/lib/net/connector/operations/topology/radware.rb +11 -0
  67. data/lib/net/connector/operations/topology/strategy.rb +53 -0
  68. data/lib/net/connector/operations/topology.rb +192 -0
  69. data/lib/net/connector/operations.rb +20 -0
  70. data/lib/net/connector/templates/cisco_cdp_neighbors_detail.textfsm +7 -0
  71. data/lib/net/connector/templates/cisco_ios_running_config_interfaces.textfsm +12 -0
  72. data/lib/net/connector/templates/cisco_ios_show_ip_interface_brief.textfsm +7 -0
  73. data/lib/net/connector/templates/h3c_interface_descriptions.textfsm +11 -0
  74. data/lib/net/connector/templates/h3c_lldp_local_first.textfsm +8 -0
  75. data/lib/net/connector/templates/h3c_lldp_name_first.textfsm +8 -0
  76. data/lib/net/connector/templates/hillstone_interface_descriptions.textfsm +11 -0
  77. data/lib/net/connector/templates/hillstone_lldp_neighbors.textfsm +7 -0
  78. data/lib/net/connector/templates/index +8 -0
  79. data/lib/net/connector/templates/palo_alto_interface_descriptions.textfsm +6 -0
  80. data/lib/net/connector/templates/palo_alto_lldp_neighbors.textfsm +11 -0
  81. data/lib/net/connector/templates/radware_port_names.textfsm +13 -0
  82. data/lib/net/connector/vendor/cisco_ios/running_config.rb +19 -0
  83. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +32 -0
  84. data/lib/net/connector/vendor/cisco_ios/topology.rb +33 -0
  85. data/lib/net/connector/vendor/cisco_ios.rb +48 -0
  86. data/lib/net/connector/vendor/cisco_nxos/running_config.rb +22 -0
  87. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +34 -0
  88. data/lib/net/connector/vendor/cisco_nxos.rb +55 -0
  89. data/lib/net/connector/vendor/h3c/tftp_backup.rb +43 -0
  90. data/lib/net/connector/vendor/h3c/topology.rb +56 -0
  91. data/lib/net/connector/vendor/h3c.rb +77 -0
  92. data/lib/net/connector/vendor/h3c_wireless.rb +14 -0
  93. data/lib/net/connector/vendor/hillstone/running_config.rb +13 -0
  94. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +40 -0
  95. data/lib/net/connector/vendor/hillstone/topology.rb +48 -0
  96. data/lib/net/connector/vendor/hillstone.rb +43 -0
  97. data/lib/net/connector/vendor/huawei/tftp_backup.rb +34 -0
  98. data/lib/net/connector/vendor/huawei.rb +64 -0
  99. data/lib/net/connector/vendor/palo_alto/running_config.rb +70 -0
  100. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +36 -0
  101. data/lib/net/connector/vendor/palo_alto/topology.rb +63 -0
  102. data/lib/net/connector/vendor/palo_alto.rb +43 -0
  103. data/lib/net/connector/vendor/radware/tftp_backup.rb +40 -0
  104. data/lib/net/connector/vendor/radware/topology.rb +14 -0
  105. data/lib/net/connector/vendor/radware.rb +62 -0
  106. data/lib/net/connector/version.rb +7 -0
  107. data/lib/net/connector.rb +48 -0
  108. data/lib/net-connector.rb +3 -0
  109. metadata +517 -0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../device/running_config/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module CiscoIos
8
+ class RunningConfig < Net::Connector::RunningConfig::Rendered
9
+ def clean(text)
10
+ config = super
11
+ config.gsub!(/^[^\n]*#\s*terminal\s+length\s+0\s*\n/i, "")
12
+ config.sub!(/^[^\n]*#\s*copy\s+run(?:ning-config)?\s+(?:start|startup-config)\s*\n.*\z/im, "")
13
+ config.gsub!(/^!.*(?:Last\s+configuration\s+change|NVRAM\s+config).*\n/i, "")
14
+ config
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/tftp/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module CiscoIos
8
+ class TftpBackup < Operations::Tftp::Strategy
9
+ # 构造当前厂商的 TFTP 导出交互脚本。
10
+ def script(target, source_file:, vrf: nil)
11
+ raise ArgumentError, "Cisco IOS does not use source_file" if source_file
12
+ raise ArgumentError, "Cisco IOS TFTP backup does not use vrf" if vrf
13
+
14
+ Script.new([Command.new("copy running-config tftp:", timeout: 180,
15
+ interactions: [
16
+ interaction(/Address\s+or\s+name\s+of\s+remote\s+host\s*\[[^\]]*\]\?\s*\z/i,
17
+ "#{target.host}\n"),
18
+ interaction(/Destination\s+filename\s*\[[^\]]*\]\?\s*\z/i,
19
+ "#{target.path}\n")
20
+ ])])
21
+ end
22
+
23
+ # 检查设备回显是否确认 TFTP 传输完成。
24
+ def complete?(result)
25
+ completion_lines(result).any? do |line|
26
+ copied_bytes?(line) || line.match?(/\A\[OK(?:\s*-\s*[1-9]\d*\s+bytes)?\]\z/i)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/topology/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module CiscoIos
8
+ class Topology < Operations::Topology::Strategy
9
+ def self.supports?(capability)
10
+ %i[neighbors interface_descriptions interface_description_changes].include?(capability)
11
+ end
12
+
13
+ def neighbor_command = "show cdp neighbors detail"
14
+ def description_template = "cisco_ios_running_config_interfaces.textfsm"
15
+ def protocol = :cdp
16
+ def enter_configuration = "configure terminal"
17
+ def finish_commands = ["end"] + @device.profile.save_commands
18
+
19
+ def expected_neighbor_count(output, _template)
20
+ output.scan(/^\s*Device ID:\s*\S+/i).size
21
+ end
22
+
23
+ def empty_neighbor_output?(output)
24
+ output.match?(/Total cdp entries displayed\s*:\s*0|no cdp neighbors/i)
25
+ end
26
+
27
+ def change_commands(change)
28
+ InterfaceDescription.commands(interface: change.interface, description: change.new_description)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../device/base"
4
+ require_relative "cisco_ios/running_config"
5
+ require_relative "cisco_ios/tftp_backup"
6
+ require_relative "cisco_ios/topology"
7
+
8
+ module Net
9
+ module Connector
10
+ module CiscoIos
11
+ # Cisco IOS 和 IOS-XE 配置采集连接器。
12
+ class Connector < Net::Connector::Base
13
+ vendor :cisco_ios
14
+ # 声明 IOS 的命令、提示、分页、诊断和保存确认规则。
15
+ profile do
16
+ running_config_strategy Net::Connector::CiscoIos::RunningConfig
17
+ tftp_strategy Net::Connector::CiscoIos::TftpBackup
18
+ topology_strategy Net::Connector::CiscoIos::Topology
19
+ commands do
20
+ running_config "terminal length 0", "show running-config"
21
+ save_config "copy running-config startup-config"
22
+ end
23
+
24
+ prompts do
25
+ login(/(?:\A|(?<=[\r\n]))\S+[>#]\s*\z/)
26
+ command(/(?:\A|(?<=[\r\n]))\S+#\s*\z/)
27
+ end
28
+
29
+ pager do
30
+ pattern(/(?:\A|(?<=[\r\n]))[^\S\r\n]*--More--[^\r\n]*(?=\r|\n|\z)/i)
31
+ end
32
+
33
+ errors do
34
+ command(/^[ \t]*%[ \t]+(?:Invalid|Incomplete|Ambiguous)\s+(?:input|command)\b/i, /^[ \t]*\^/)
35
+ end
36
+
37
+ command_timeout 60
38
+
39
+ interactions do
40
+ confirm(/Destination\s+filename\s+\[[^\]]+\]\?\s*\z/i, response: "\n")
41
+ confirm(%r{Do\s+you\s+want\s+to\s+overwrite\?\s*\[yes/no\]\s*\z}i, response: "yes\n")
42
+ confirm(/Proceed\s+with\s+copy\?\s*\[confirm\]\s*\z/i, response: "\n")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../cisco_ios/running_config"
4
+
5
+ module Net
6
+ module Connector
7
+ module CiscoNxos
8
+ class RunningConfig < CiscoIos::RunningConfig
9
+ def clean(text)
10
+ without_pager = text.to_s.gsub(
11
+ /[ \t]*(?:\x1b\[[0-9;]*[A-Za-z])*--More--
12
+ (?:\x1b\[[0-9;]*[A-Za-z])*(?:[\r\x00\x08 ]|\x1b\[[0-9;]*[A-Za-z])*/ix, ""
13
+ )
14
+ config = super(without_pager)
15
+ config.sub!(/\A.*?^(?=[^\n]*#\s*show\s+running-config\s*$)/im, "")
16
+ config.gsub(/^[ \t]*(?:\[[# ]*\]|\])[ \t]*\d+%[ \t]*\n?/i, "")
17
+ .gsub(/^[ \t]*Copy\s+complete\b[^\n]*\n?/i, "")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/tftp/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module CiscoNxos
8
+ class TftpBackup < Operations::Tftp::Strategy
9
+ # 构造当前厂商的 TFTP 导出交互脚本。
10
+ def script(target, source_file:, vrf: nil)
11
+ raise ArgumentError, "Cisco NX-OS does not use source_file" if source_file
12
+
13
+ vrf ||= "management"
14
+ command = "copy running-config tftp://#{target.host}/#{target.path} vrf #{vrf}"
15
+ Script.new([Command.new(command, timeout: 180,
16
+ interactions: [
17
+ interaction(/Enter\s+vrf\s*\([^\r\n]*\):\s*\z/i,
18
+ "#{vrf}\n")
19
+ ])])
20
+ end
21
+
22
+ # 检查设备回显是否确认 TFTP 传输完成。
23
+ def complete?(result)
24
+ completion_lines(result).any? do |line|
25
+ line.match?(/\A
26
+ (?:transfer|copy)(?:\s+operation)?\s+
27
+ (?:success(?:ful(?:ly)?)?|succeeded|complete(?:d)?(?:\s+successfully)?)[.!]?
28
+ \z/ix) || copied_bytes?(line)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../device/base"
4
+ require_relative "cisco_nxos/running_config"
5
+ require_relative "cisco_nxos/tftp_backup"
6
+ require_relative "cisco_ios/topology"
7
+
8
+ module Net
9
+ module Connector
10
+ module CiscoNxos
11
+ # Cisco Nexus 9000 和 NX-OS 配置采集连接器。
12
+ class Connector < Net::Connector::Base
13
+ vendor :cisco_nxos
14
+ # 声明 NX-OS 的命令、提示、分页、诊断和保存确认规则。
15
+ profile do
16
+ running_config_strategy Net::Connector::CiscoNxos::RunningConfig
17
+ tftp_strategy Net::Connector::CiscoNxos::TftpBackup
18
+ topology_strategy Net::Connector::CiscoIos::Topology
19
+ commands do
20
+ running_config "terminal length 0", "show running-config"
21
+ save_config "copy run start"
22
+ end
23
+
24
+ prompts do
25
+ login(/\S+[>#]\s*\z/)
26
+ command(/\S+#\s*\z/)
27
+ end
28
+
29
+ pager do
30
+ pattern(/(?:\A|(?<=[\r\n]))[ \t]*(?:\x1b\[[0-9;]*[A-Za-z])*--More--
31
+ (?:[ \t\x00\x08]|\x1b\[[0-9;]*[A-Za-z])*(?=\r|\n|\z)/ix)
32
+ end
33
+
34
+ errors do
35
+ command(
36
+ /^[ \t]*%?[ \t]*(?:(?:Invalid|Ambiguous|Incomplete)\s+command(?:\s+\([^)]+\))?|
37
+ Invalid\s+parameter\s+detected)\s+at\s+'\^'\s+marker\./ix,
38
+ /^[ \t]*\^/,
39
+ /^[ \t]*%[ \t]+(?:Invalid|Ambiguous|Incomplete)\s+command\b/i,
40
+ /^[ \t]*%?[ \t]*Invalid\s+parameter\s+detected\b/i,
41
+ /^[ \t]*syntax\s+error\b/i
42
+ )
43
+ end
44
+
45
+ command_timeout 60
46
+
47
+ interactions do
48
+ confirm(/Destination\s+filename\s+\[[^\]]+\]\?\s*\z/i, response: "\n")
49
+ confirm(%r{Do\s+you\s+want\s+to\s+overwrite\?\s*\[yes/no\]\s*\z}i, response: "yes\n")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/tftp/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module H3c
8
+ class TftpBackup < Operations::Tftp::Strategy
9
+ # 选取并校验 H3C 启动配置文件路径。
10
+ def source_file(value)
11
+ return TftpTarget.validate_source_file!(value) if value
12
+
13
+ output = @device.execute("display startup").value!
14
+ rendered = TerminalRenderer.render(output)
15
+ path = rendered[/^\s*Next main startup saved-configuration file:\s*(\S+)/i, 1]
16
+ path ||= rendered[/^\s*Current startup saved-configuration file:\s*(\S+)/i, 1]
17
+ if path.nil? || path.casecmp?("NULL")
18
+ raise DeviceError.new("device has no saved startup configuration",
19
+ code: :startup_config_missing, host: @device.host, phase: :tftp_backup)
20
+ end
21
+
22
+ TftpTarget.validate_source_file!(path.sub(/\(\*\)\z/, ""))
23
+ end
24
+
25
+ # 根据 H3C 源文件确定默认导出名称。
26
+ def default_path(source_file) = File.basename(source_file)
27
+
28
+ # 构造当前厂商的 TFTP 导出交互脚本。
29
+ def script(target, source_file:, vrf: nil)
30
+ raise ArgumentError, "H3C TFTP backup does not use vrf" if vrf
31
+
32
+ destination = target.explicit_path? ? " #{target.path}" : ""
33
+ Script.new([Command.new("tftp #{target.host} put #{source_file}#{destination}", timeout: 180)])
34
+ end
35
+
36
+ # 检查设备回显是否确认 TFTP 传输完成。
37
+ def complete?(result)
38
+ completion_lines(result).any? { |line| completed_upload?(line) }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/topology/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module H3c
8
+ class Topology < Operations::Topology::Strategy
9
+ TABLE_HEADER = /^\s*(?:System Name\s+Local Interface\b|(?:Local Interface|LocalIf)\s+)/i
10
+ def self.supports?(capability)
11
+ %i[neighbors interface_descriptions interface_description_changes].include?(capability)
12
+ end
13
+
14
+ def neighbor_command = "display lldp neighbor-information list"
15
+ def description_template = "h3c_interface_descriptions.textfsm"
16
+ def enter_configuration = "system-view"
17
+ def finish_commands = ["return"] + @device.profile.save_commands
18
+
19
+ def neighbor_template(output)
20
+ return "h3c_lldp_name_first.textfsm" if output.match?(/^\s*System Name\s+Local Interface\b/im)
21
+ return "h3c_lldp_local_first.textfsm" if output.match?(/^\s*(?:Local Interface|LocalIf)\s+/im)
22
+
23
+ raise ParsingError.new("H3C LLDP table header was not recognized", code: :unrecognized_output,
24
+ host: @device.host, phase: :discover)
25
+ end
26
+
27
+ def expected_neighbor_count(output, template)
28
+ local_column = template == "h3c_lldp_name_first.textfsm" ? 1 : 0
29
+ lines = neighbor_lines(output)
30
+ return unless lines.all? do |line|
31
+ interface = line.split.fetch(local_column, nil)
32
+ interface&.match?(/\A[A-Za-z][A-Za-z-]*\d+(?:\/\d+)+\z/)
33
+ end
34
+
35
+ lines.size
36
+ end
37
+
38
+ # 表头之后的未知行不能作为零邻居证据。
39
+ def empty_neighbor_output?(output) = neighbor_lines(output).empty?
40
+
41
+ def change_commands(change)
42
+ InterfaceDescription.commands(interface: change.interface, description: change.new_description, leave: "quit")
43
+ end
44
+
45
+ private
46
+
47
+ def neighbor_lines(output)
48
+ output.lines.drop_while { |line| !line.match?(TABLE_HEADER) }.map(&:strip).reject do |line|
49
+ line.empty? || line.match?(TABLE_HEADER) || line.match?(/\A[-=+ ]+\z/) ||
50
+ line.match?(/\A(?:<[^>]+>|\[[^\]]+\])\z/)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../device/base"
4
+ require_relative "h3c/tftp_backup"
5
+ require_relative "h3c/topology"
6
+
7
+ module Net
8
+ module Connector
9
+ module H3c
10
+ # H3C 直接使用登录后的命令视图,并在单个批次内解析设备分配的规则编号。
11
+ class Connector < Net::Connector::Base
12
+ vendor :h3c
13
+ # 声明 H3C 的命令、提示、诊断和保存确认规则。
14
+ profile do
15
+ running_config_strategy Net::Connector::RunningConfig::Rendered
16
+ tftp_strategy Net::Connector::H3c::TftpBackup
17
+ topology_strategy Net::Connector::H3c::Topology
18
+ commands do
19
+ running_config "dis cur"
20
+ save_config "save force"
21
+ end
22
+
23
+ prompts do
24
+ login(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*\S+[>\]](?:[^\S\n]|\x00)*\z/)
25
+ command(/(?:\A|(?<=[\r\n]))(?:[^\S\n]|\x00)*[^\r\n]+[>\]](?:[^\S\n]|\x00)*\z/)
26
+ username(/(?:login|Username):\s*\z/i)
27
+ end
28
+
29
+ errors do
30
+ command(
31
+ /
32
+ ^[ \t]*%[ \t]+(?:(?:Unrecognized|Ambiguous|Incomplete)\s+command|Too\s+many\s+parameters)
33
+ \s+found\s+at\s+'\^'\s+position\.
34
+ /ix,
35
+ /^[ \t]*error:/i,
36
+ /^[ \t]*Permission\s+denied\./i,
37
+ /^\s*\^/
38
+ )
39
+ end
40
+
41
+ command_timeout 10
42
+
43
+ interactions do
44
+ confirm %r{Are\s+you\s+sure\?\s*\[Y/N\]}i, response: "y\n"
45
+ confirm(/press\s+the\s+enter\s+key\)/i, response: "\n")
46
+ confirm %r{Continue\?\s*\[Y/N\]}i, response: "y\n"
47
+ confirm(/Please\s+input\s+the\s+file\s+name\(\*\.cfg\)\[[^\]]+\]\s*\z/i, response: "\n")
48
+ confirm %r{overwrite\?\s*\[Y/N\]}i, response: "y\n"
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ # 发送规则追加命令前,用本批次已获取的真实规则编号替换 XXX 占位符。
55
+ def prepare_command(command, execution)
56
+ return command unless /\A[ \t]*rule[ \t]+XXX[ \t]+append\b/i.match?(command.text)
57
+
58
+ rule_id = execution.context[:rule_id]
59
+ raise execution.failure("new rule ID has not been acquired") unless rule_id
60
+
61
+ command.with_text(command.text.sub(/XXX/i, rule_id))
62
+ end
63
+
64
+ # 规则创建成功后查询当前配置,提取设备分配的编号并保存到本批次变量。
65
+ def after_command(command, _response, execution)
66
+ return unless /\A[ \t]*rule[ \t]+pass\b/i.match?(command.text)
67
+
68
+ response = execution.query("dis this")
69
+ rule_id = response.raw[/rule\s+(\d+)\s+pass[^\n]+\n\s*#/i, 1]
70
+ raise execution.failure("device did not return the new rule ID") unless rule_id
71
+
72
+ execution.context[:rule_id] = rule_id
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "h3c"
4
+
5
+ module Net
6
+ module Connector
7
+ module H3cWireless
8
+ # H3C 无线控制器沿用 Comware CLI,对无线设备单独暴露厂商入口。
9
+ class Connector < H3c::Connector
10
+ vendor :h3c_wireless
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../device/running_config/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module Hillstone
8
+ class RunningConfig < Net::Connector::RunningConfig::Strategy
9
+ def clean(text) = text.gsub(/\x00?\x08+[ \t]+\x08+/, "")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/tftp/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module Hillstone
8
+ class TftpBackup < Operations::Tftp::Strategy
9
+ # 构造当前厂商的 TFTP 导出交互脚本。
10
+ def script(target, source_file:, vrf: nil)
11
+ raise ArgumentError, "Hillstone TFTP export does not use source_file" if source_file
12
+ if target.explicit_path? && target.path.include?("/")
13
+ raise ArgumentError, "Hillstone TFTP filename cannot contain a directory"
14
+ end
15
+
16
+ command = "export configuration startup to tftp server #{target.host} vrouter #{vrf || "mgt-vr"}"
17
+ command += " #{target.path}" if target.explicit_path?
18
+ Script.new([Command.new(command, timeout: 180)])
19
+ end
20
+
21
+ # 检查设备回显是否确认 TFTP 传输完成。
22
+ def complete?(result)
23
+ !completion_line(result).nil?
24
+ end
25
+
26
+ # 从山石成功回显提取实际目标文件名。
27
+ def remote_path(_target, result)
28
+ filename = completion_line(result)&.split&.last
29
+ TftpTarget.validate_path!(filename)
30
+ end
31
+
32
+ private
33
+
34
+ def completion_line(result)
35
+ completion_lines(result).find { |line| line.match?(/\AExport\s+ok\s*,\s*target\s+file\s+name\s+\S+\z/i) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/topology/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module Hillstone
8
+ class Topology < Operations::Topology::Strategy
9
+ def self.supports?(capability)
10
+ %i[neighbors interface_descriptions interface_description_changes].include?(capability)
11
+ end
12
+
13
+ def neighbor_command = "show lldp neighbor-information"
14
+ def description_template = "hillstone_interface_descriptions.textfsm"
15
+ def enter_configuration = "configure"
16
+ def finish_commands = ["exit"] + @device.profile.save_commands
17
+
18
+ def expected_neighbor_count(output, _template)
19
+ reported = output[/Total lldp neighbor number:\s*(\d+)/i, 1]
20
+ lines = neighbor_lines(output)
21
+ return unless lines.all? { |line| line.match?(/\A\S+\s+ethernet\d+\/\d+\s/i) }
22
+ return if reported && reported.to_i != lines.size
23
+
24
+ lines.size
25
+ end
26
+
27
+ def empty_neighbor_output?(output)
28
+ neighbor_lines(output).empty? && (output.match?(/Total lldp neighbor number:\s*0\b/i) ||
29
+ output.match?(/System Name\s+Local Interface/i))
30
+ end
31
+
32
+ def change_commands(change)
33
+ InterfaceDescription.commands(interface: change.interface, description: change.new_description)
34
+ end
35
+
36
+ private
37
+
38
+ def neighbor_lines(output)
39
+ output.lines.map(&:strip).reject do |line|
40
+ line.empty? || line == neighbor_command || line.match?(/\ASystem Name\s+Local Interface\b/i) ||
41
+ line.match?(/\ATotal lldp neighbor number:\s*\d+\.?\z/i) ||
42
+ line.match?(/\A[-=+ ]+\z/) || line.match?(/\A\S+(?:#|\([MBF]\))\z/)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../device/base"
4
+ require_relative "hillstone/running_config"
5
+ require_relative "hillstone/tftp_backup"
6
+ require_relative "hillstone/topology"
7
+
8
+ module Net
9
+ module Connector
10
+ module Hillstone
11
+ # 山石 StoneOS 命令行和原生启动配置导出连接器。
12
+ class Connector < Net::Connector::Base
13
+ vendor :hillstone
14
+ profile do
15
+ running_config_strategy Net::Connector::Hillstone::RunningConfig
16
+ tftp_strategy Net::Connector::Hillstone::TftpBackup
17
+ topology_strategy Net::Connector::Hillstone::Topology
18
+ commands do
19
+ running_config "terminal length 0", "show configuration running"
20
+ save_config "save all"
21
+ end
22
+
23
+ prompts do
24
+ login(/(?:\A|(?<=[\r\n]))\S+(?:[#>$]|\([MBF]\))\s*\z/)
25
+ command(/(?:\A|(?<=[\r\n]))\S+(?:#|\([MBF]\))\s*\z/)
26
+ end
27
+
28
+ pager do
29
+ pattern(/(?:\A|(?<=[\r\n]))[ \t]*--More--[ \t\x00\x08]*(?=\r|\n|\z)/i)
30
+ end
31
+
32
+ errors do
33
+ command(/^[ \t]*\^-+/i, /^\s*\^/,
34
+ /^[ \t]*(?:Unknown|Invalid|Error)\s+command/i,
35
+ /^[ \t]*Error:\s*[^\r\n]+/i)
36
+ end
37
+
38
+ command_timeout 60
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../operations/tftp/strategy"
4
+
5
+ module Net
6
+ module Connector
7
+ module Huawei
8
+ class TftpBackup < Operations::Tftp::Strategy
9
+ # 要求调用方提供合法的华为启动配置文件路径。
10
+ def source_file(value)
11
+ raise ArgumentError, "Huawei TFTP backup requires source_file" unless value
12
+
13
+ TftpTarget.validate_source_file!(value)
14
+ end
15
+
16
+ # 根据华为配置源文件确定默认导出名称。
17
+ def default_path(source_file) = File.basename(source_file)
18
+
19
+ # 构造当前厂商的 TFTP 导出交互脚本。
20
+ def script(target, source_file:, vrf: nil)
21
+ raise ArgumentError, "Huawei TFTP backup does not use vrf" if vrf
22
+
23
+ destination = target.explicit_path? ? " #{target.path}" : ""
24
+ Script.new([Command.new("tftp #{target.host} put #{source_file}#{destination}", timeout: 180)])
25
+ end
26
+
27
+ # 检查设备回显是否确认 TFTP 传输完成。
28
+ def complete?(result)
29
+ completion_lines(result).any? { |line| completed_upload?(line) }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end