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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +82 -0
- data/LICENSE +21 -0
- data/README.md +277 -0
- data/docs/RELEASING.md +81 -0
- data/docs/VERIFICATION.md +73 -0
- data/docs/architecture.md +268 -0
- data/exe/net-connector-backup +6 -0
- data/lib/net/connector/device/base.rb +368 -0
- data/lib/net/connector/device/interface_description.rb +41 -0
- data/lib/net/connector/device/interface_name.rb +56 -0
- data/lib/net/connector/device/profile.rb +446 -0
- data/lib/net/connector/device/running_config/strategy.rb +29 -0
- data/lib/net/connector/device/running_config.rb +69 -0
- data/lib/net/connector/engine/authentication.rb +77 -0
- data/lib/net/connector/engine/base.rb +4 -0
- data/lib/net/connector/engine/command.rb +101 -0
- data/lib/net/connector/engine/configuration.rb +131 -0
- data/lib/net/connector/engine/core.rb +6 -0
- data/lib/net/connector/engine/dialogue.rb +223 -0
- data/lib/net/connector/engine/errors.rb +183 -0
- data/lib/net/connector/engine/execution.rb +64 -0
- data/lib/net/connector/engine/log.rb +227 -0
- data/lib/net/connector/engine/log_messages.rb +47 -0
- data/lib/net/connector/engine/profile.rb +4 -0
- data/lib/net/connector/engine/recovery.rb +57 -0
- data/lib/net/connector/engine/result.rb +67 -0
- data/lib/net/connector/engine/session.rb +333 -0
- data/lib/net/connector/engine/terminal_renderer.rb +158 -0
- data/lib/net/connector/engine/transport.rb +184 -0
- data/lib/net/connector/engine.rb +4 -0
- data/lib/net/connector/netdisco/batch.rb +71 -0
- data/lib/net/connector/netdisco/cli.rb +142 -0
- data/lib/net/connector/netdisco/client.rb +148 -0
- data/lib/net/connector/netdisco/config_file.rb +87 -0
- data/lib/net/connector/netdisco/device.rb +75 -0
- data/lib/net/connector/netdisco/fleet.rb +258 -0
- data/lib/net/connector/netdisco/planner.rb +77 -0
- data/lib/net/connector/netdisco/result_store.rb +43 -0
- data/lib/net/connector/netdisco/rules.rb +131 -0
- data/lib/net/connector/netdisco/settings.rb +183 -0
- data/lib/net/connector/netdisco/worker.rb +75 -0
- data/lib/net/connector/netdisco.rb +14 -0
- data/lib/net/connector/operations/local_backup.rb +49 -0
- data/lib/net/connector/operations/parse_output.rb +60 -0
- data/lib/net/connector/operations/private_file.rb +25 -0
- data/lib/net/connector/operations/running_config/cisco.rb +14 -0
- data/lib/net/connector/operations/running_config/hillstone.rb +12 -0
- data/lib/net/connector/operations/running_config/palo_alto.rb +12 -0
- data/lib/net/connector/operations/running_config/strategy.rb +3 -0
- data/lib/net/connector/operations/running_config.rb +16 -0
- data/lib/net/connector/operations/saved_config.rb +72 -0
- data/lib/net/connector/operations/tftp/cisco_ios.rb +11 -0
- data/lib/net/connector/operations/tftp/cisco_nxos.rb +11 -0
- data/lib/net/connector/operations/tftp/h3c.rb +11 -0
- data/lib/net/connector/operations/tftp/hillstone.rb +11 -0
- data/lib/net/connector/operations/tftp/huawei.rb +11 -0
- data/lib/net/connector/operations/tftp/palo_alto.rb +11 -0
- data/lib/net/connector/operations/tftp/radware.rb +11 -0
- data/lib/net/connector/operations/tftp/strategy.rb +77 -0
- data/lib/net/connector/operations/tftp_backup.rb +120 -0
- data/lib/net/connector/operations/topology/cisco.rb +11 -0
- data/lib/net/connector/operations/topology/h3c.rb +11 -0
- data/lib/net/connector/operations/topology/hillstone.rb +11 -0
- data/lib/net/connector/operations/topology/palo_alto.rb +11 -0
- data/lib/net/connector/operations/topology/radware.rb +11 -0
- data/lib/net/connector/operations/topology/strategy.rb +53 -0
- data/lib/net/connector/operations/topology.rb +192 -0
- data/lib/net/connector/operations.rb +20 -0
- data/lib/net/connector/templates/cisco_cdp_neighbors_detail.textfsm +7 -0
- data/lib/net/connector/templates/cisco_ios_running_config_interfaces.textfsm +12 -0
- data/lib/net/connector/templates/cisco_ios_show_ip_interface_brief.textfsm +7 -0
- data/lib/net/connector/templates/h3c_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/h3c_lldp_local_first.textfsm +8 -0
- data/lib/net/connector/templates/h3c_lldp_name_first.textfsm +8 -0
- data/lib/net/connector/templates/hillstone_interface_descriptions.textfsm +11 -0
- data/lib/net/connector/templates/hillstone_lldp_neighbors.textfsm +7 -0
- data/lib/net/connector/templates/index +8 -0
- data/lib/net/connector/templates/palo_alto_interface_descriptions.textfsm +6 -0
- data/lib/net/connector/templates/palo_alto_lldp_neighbors.textfsm +11 -0
- data/lib/net/connector/templates/radware_port_names.textfsm +13 -0
- data/lib/net/connector/vendor/cisco_ios/running_config.rb +19 -0
- data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +32 -0
- data/lib/net/connector/vendor/cisco_ios/topology.rb +33 -0
- data/lib/net/connector/vendor/cisco_ios.rb +48 -0
- data/lib/net/connector/vendor/cisco_nxos/running_config.rb +22 -0
- data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/cisco_nxos.rb +55 -0
- data/lib/net/connector/vendor/h3c/tftp_backup.rb +43 -0
- data/lib/net/connector/vendor/h3c/topology.rb +56 -0
- data/lib/net/connector/vendor/h3c.rb +77 -0
- data/lib/net/connector/vendor/h3c_wireless.rb +14 -0
- data/lib/net/connector/vendor/hillstone/running_config.rb +13 -0
- data/lib/net/connector/vendor/hillstone/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/hillstone/topology.rb +48 -0
- data/lib/net/connector/vendor/hillstone.rb +43 -0
- data/lib/net/connector/vendor/huawei/tftp_backup.rb +34 -0
- data/lib/net/connector/vendor/huawei.rb +64 -0
- data/lib/net/connector/vendor/palo_alto/running_config.rb +70 -0
- data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +36 -0
- data/lib/net/connector/vendor/palo_alto/topology.rb +63 -0
- data/lib/net/connector/vendor/palo_alto.rb +43 -0
- data/lib/net/connector/vendor/radware/tftp_backup.rb +40 -0
- data/lib/net/connector/vendor/radware/topology.rb +14 -0
- data/lib/net/connector/vendor/radware.rb +62 -0
- data/lib/net/connector/version.rb +7 -0
- data/lib/net/connector.rb +48 -0
- data/lib/net-connector.rb +3 -0
- metadata +517 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "huawei/tftp_backup"
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module Connector
|
|
8
|
+
module Huawei
|
|
9
|
+
# Huawei 命令行连接器;提权延迟到命令批次开始时执行。
|
|
10
|
+
class Connector < Net::Connector::Base
|
|
11
|
+
vendor :huawei
|
|
12
|
+
# 声明 Huawei 的命令、提示、诊断、提权和保存确认规则。
|
|
13
|
+
profile do
|
|
14
|
+
running_config_strategy Net::Connector::RunningConfig::Rendered
|
|
15
|
+
tftp_strategy Net::Connector::Huawei::TftpBackup
|
|
16
|
+
commands do
|
|
17
|
+
running_config "dis cur"
|
|
18
|
+
save_config "save force"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
prompts do
|
|
22
|
+
login(/(?:\A|(?<=[\r\n]))\s*\S+[>\]]\s*\z/)
|
|
23
|
+
command(/(?:\A|(?<=[\r\n]))[^\r\n]+[>\]]\s*\z/)
|
|
24
|
+
username(/(?:login|Username):\s*\z/i)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
errors do
|
|
28
|
+
command(
|
|
29
|
+
/^\s*\^/,
|
|
30
|
+
/^[ \t]*error:/i,
|
|
31
|
+
/^[ \t]*Permission\s+denied\./i,
|
|
32
|
+
/
|
|
33
|
+
^[ \t]*%[ \t]+(?:(?:Unrecognized|Ambiguous|Incomplete)\s+command|Too\s+many\s+parameters)
|
|
34
|
+
\s+found\s+at\s+'\^'\s+position\.
|
|
35
|
+
/ix
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
privilege do
|
|
40
|
+
command "su"
|
|
41
|
+
prompt(/privilege\s+(?:level\s+is|is\s+).+>\s*\z/im)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
command_timeout 10
|
|
45
|
+
|
|
46
|
+
interactions do
|
|
47
|
+
confirm %r{Are\s+you\s+sure\?\s*\[Y/N\]}i, response: "y\n"
|
|
48
|
+
confirm(/press\s+the\s+enter\s+key\)/i, response: "\n")
|
|
49
|
+
confirm %r{Continue\?\s*\[Y/N\]}i, response: "y\n"
|
|
50
|
+
confirm(/Please\s+input\s+the\s+file\s+name\(\*\.cfg\)\[[^\]]+\]\s*\z/i, response: "\n")
|
|
51
|
+
confirm %r{overwrite\?\s*\[Y/N\]}i, response: "y\n"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
protected
|
|
56
|
+
|
|
57
|
+
# 登录后若提示符以 ] 结尾,则确认设备已经处于特权视图。
|
|
58
|
+
def after_login(session, response)
|
|
59
|
+
session.mark_privileged! if response.prompt.include?("]")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require_relative "../../device/running_config/strategy"
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module Connector
|
|
8
|
+
module PaloAlto
|
|
9
|
+
class RunningConfig < Net::Connector::RunningConfig::Strategy
|
|
10
|
+
def result_step(result)
|
|
11
|
+
result.steps.find { |step| step.command.text == "show" }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# 采集切换视图时只改变结束符,仍绑定已认证的设备身份。
|
|
15
|
+
def prompt_text(command)
|
|
16
|
+
prompt = super
|
|
17
|
+
case command.text
|
|
18
|
+
when "configure" then prompt.sub(/[>#]\z/, "#")
|
|
19
|
+
when "exit" then prompt.sub(/[>#]\z/, ">")
|
|
20
|
+
else prompt
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_response(command, response, _execution)
|
|
25
|
+
return unless command.text == "show config diff"
|
|
26
|
+
return if body(response.output, command.text).empty?
|
|
27
|
+
|
|
28
|
+
raise DeviceError.new("PAN-OS candidate differs from running configuration",
|
|
29
|
+
code: :uncommitted_configuration, host: @device.host, phase: :collect)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clean(text)
|
|
33
|
+
normalized = body(text, "show").sub(/(?:\A|\n)[ \t]*\[edit\][ \t]*\z/, "").strip
|
|
34
|
+
unless set_commands?(normalized)
|
|
35
|
+
raise DeviceError.new("PAN-OS configuration must be set format",
|
|
36
|
+
code: :unsupported_configuration_format, host: @device.host, phase: :collect)
|
|
37
|
+
end
|
|
38
|
+
normalized
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# 证书等引号内换行必须闭合,不能将截断文本当成配置。
|
|
44
|
+
def set_commands?(text)
|
|
45
|
+
return false if text.empty?
|
|
46
|
+
|
|
47
|
+
pending = +""
|
|
48
|
+
text.each_line do |line|
|
|
49
|
+
next if pending.empty? && line.strip.empty?
|
|
50
|
+
return false if pending.empty? && !line.start_with?("set ")
|
|
51
|
+
|
|
52
|
+
pending << line
|
|
53
|
+
begin
|
|
54
|
+
Shellwords.split(pending)
|
|
55
|
+
pending.clear
|
|
56
|
+
rescue ArgumentError
|
|
57
|
+
# 未闭合引号继续读取;末尾仍未闭合则拒绝整个采集结果。
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
pending.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def body(text, command)
|
|
64
|
+
text.gsub("\r\n", "\n").sub(/\A[ \t]*#{Regexp.escape(command)}[ \t]*\n/, "")
|
|
65
|
+
.sub(@device.profile.command_prompt, "").strip
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/tftp/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module PaloAlto
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 返回 PAN-OS 固定使用的运行配置文件名。
|
|
10
|
+
def default_path(_source_file) = "running-config.xml"
|
|
11
|
+
|
|
12
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
13
|
+
def script(target, source_file:, vrf: nil)
|
|
14
|
+
raise ArgumentError, "PAN-OS does not use source_file" if source_file
|
|
15
|
+
raise ArgumentError, "PAN-OS TFTP backup does not use vrf" if vrf
|
|
16
|
+
unless target.path == "running-config.xml"
|
|
17
|
+
raise ArgumentError, "PAN-OS TFTP export uses the fixed filename running-config.xml"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Script.new([Command.new("tftp export configuration to #{target.host} from running-config.xml",
|
|
21
|
+
timeout: 180)])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
25
|
+
def complete?(result)
|
|
26
|
+
completion_lines(result).any? do |line|
|
|
27
|
+
line.match?(/\ASent\s+[1-9]\d*\s+bytes(?:\s+in\s+[\d.]+\s+(?:secs?|seconds?))?[.!]?\z/i)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 返回 PAN-OS 实际上传的固定文件名。
|
|
32
|
+
def remote_path(_target, _result) = "running-config.xml"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/topology/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module PaloAlto
|
|
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 neighbors all"
|
|
14
|
+
def description_template = "palo_alto_interface_descriptions.textfsm"
|
|
15
|
+
def enter_configuration = "configure"
|
|
16
|
+
def finish_commands = ["commit", "exit"]
|
|
17
|
+
|
|
18
|
+
def expected_neighbor_count(output, _template)
|
|
19
|
+
neighbor_blocks(output).count { |block| !empty_neighbor_block?(block) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def empty_neighbor_output?(output)
|
|
23
|
+
return true if output.match?(/No LLDP neighbors/i)
|
|
24
|
+
|
|
25
|
+
blocks = neighbor_blocks(output)
|
|
26
|
+
!blocks.empty? && blocks.all? { |block| empty_neighbor_block?(block) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# PAN-OS 会列出启用 LLDP、但没有邻居的本机接口;只有明确为空的
|
|
32
|
+
# Neighbor information 块可跳过,缺字段的非空块继续交给数量校验拒绝。
|
|
33
|
+
def neighbor_blocks(output)
|
|
34
|
+
output.split(/^\s*Local information:\s*$/i).select { |block| block.match?(/^\s*Local interface:\s*\S+/i) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def empty_neighbor_block?(block)
|
|
38
|
+
tail = block.split(/^\s*Neighbor information:\s*$/i, 2)
|
|
39
|
+
tail.size == 2 && tail.last.lines.all? { |line| line.strip.empty? || line.match?(/^\S+[>#]\s*$/) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
public
|
|
43
|
+
|
|
44
|
+
# 模板按行读取;未闭合的引号表示证据不完整,不能把首行当成旧描述。
|
|
45
|
+
def decode_description(value)
|
|
46
|
+
value = value.strip
|
|
47
|
+
value.start_with?('"') ? Shellwords.split(value).join(" ") : value
|
|
48
|
+
rescue ArgumentError
|
|
49
|
+
raise ParsingError.new("PAN-OS interface comment is not a complete single-line value",
|
|
50
|
+
code: :unrecognized_output, host: @device.host, phase: :parse)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def change_commands(change)
|
|
54
|
+
[%Q(set network interface ethernet #{change.interface} comment "#{change.new_description}")]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def script_command(command)
|
|
58
|
+
command == "commit" ? Command.new(command, timeout: 300) : command
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "palo_alto/running_config"
|
|
5
|
+
require_relative "palo_alto/tftp_backup"
|
|
6
|
+
require_relative "palo_alto/topology"
|
|
7
|
+
|
|
8
|
+
module Net
|
|
9
|
+
module Connector
|
|
10
|
+
module PaloAlto
|
|
11
|
+
# Palo Alto Networks PAN-OS 运维命令行和运行配置采集连接器。
|
|
12
|
+
class Connector < Net::Connector::Base
|
|
13
|
+
vendor :palo_alto
|
|
14
|
+
# 声明 PAN-OS 的命令、提示、超时、诊断和登录确认规则。
|
|
15
|
+
profile do
|
|
16
|
+
running_config_strategy Net::Connector::PaloAlto::RunningConfig
|
|
17
|
+
tftp_strategy Net::Connector::PaloAlto::TftpBackup
|
|
18
|
+
topology_strategy Net::Connector::PaloAlto::Topology
|
|
19
|
+
commands do
|
|
20
|
+
running_config "set cli pager off", "set cli config-output-format set", "show config diff",
|
|
21
|
+
"configure", "show", "exit", "show config diff"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
prompts do
|
|
25
|
+
login(/(?:\A|(?<=[\r\n]))(?:[^\s@]+)@[^\s>#]+[>#]\s*\z/)
|
|
26
|
+
command(/(?:\A|(?<=[\r\n]))(?:[^\s@]+)@[^\s>#]+[>#]\s*\z/)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
errors do
|
|
30
|
+
command(/^[ \t]*(?:Invalid syntax|Unknown command|Server error|Command failed)(?:[.:]|\s)/i)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
command_timeout 120
|
|
34
|
+
|
|
35
|
+
interactions do
|
|
36
|
+
login %r{(?:acknowledge|accept)[\s\S]*(?:yes/no|\[y/n\]|enter\s+yes)[^\r\n]*\z}i,
|
|
37
|
+
response: "yes\n"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
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 Radware
|
|
8
|
+
class TftpBackup < Operations::Tftp::Strategy
|
|
9
|
+
# 生成 Radware 压缩配置的远端文件名。
|
|
10
|
+
def default_path(_source_file) = TftpTarget.filename(@device.host, extension: "tgz")
|
|
11
|
+
|
|
12
|
+
# 构造当前厂商的 TFTP 导出交互脚本。
|
|
13
|
+
def script(target, source_file:, vrf: nil)
|
|
14
|
+
raise ArgumentError, "Radware Alteon does not use source_file" if source_file
|
|
15
|
+
raise ArgumentError, "Radware TFTP backup does not use vrf" if vrf
|
|
16
|
+
|
|
17
|
+
Script.new([Command.new("/cfg/ptcfg #{target.host} -tftp", timeout: 180, interactions: [
|
|
18
|
+
interaction(/Enter\s+(?:hostname|IP address)[^\r\n]*:\s*\z/i, "#{target.host}\n"),
|
|
19
|
+
interaction(/Enter\s+name\s+of\s+(?:\.tgz\s+)?file[^\r\n]*:\s*\z/i, "#{target.path}\n"),
|
|
20
|
+
interaction(/Enter\s+username[^\r\n]*:\s*\z/i, "\n"),
|
|
21
|
+
interaction(/Include\s+private\s+keys\?\s*\[y\/n\]:\s*\z/i, "n\n"),
|
|
22
|
+
interaction(/Enter\s+"?mansync"?\s+to\s+get\s+real\/group\/virt\s+internal\s+index\s+config:\s*\z/i,
|
|
23
|
+
"mansync\n")
|
|
24
|
+
])])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 检查设备回显是否确认 TFTP 传输完成。
|
|
28
|
+
def complete?(result)
|
|
29
|
+
completion_lines(result).any? do |line|
|
|
30
|
+
line.match?(/\A
|
|
31
|
+
(?:Current\s+)?(?:configuration|config)\s+
|
|
32
|
+
(?:successfully\s+(?:uploaded|tftp'd)|(?:uploaded|tftp'd)(?:\s+successfully)?|
|
|
33
|
+
complete(?:d)?(?:\s+successfully)?)[.!]?
|
|
34
|
+
\z/ix)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../operations/topology/strategy"
|
|
4
|
+
|
|
5
|
+
module Net
|
|
6
|
+
module Connector
|
|
7
|
+
module Radware
|
|
8
|
+
class Topology < Operations::Topology::Strategy
|
|
9
|
+
def self.supports?(capability) = capability == :interface_descriptions
|
|
10
|
+
def description_template = "radware_port_names.textfsm"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../device/base"
|
|
4
|
+
require_relative "radware/tftp_backup"
|
|
5
|
+
require_relative "radware/topology"
|
|
6
|
+
|
|
7
|
+
module Net
|
|
8
|
+
module Connector
|
|
9
|
+
module Radware
|
|
10
|
+
# Radware Alteon 和 DefensePro 命令行配置采集连接器。
|
|
11
|
+
class Connector < Net::Connector::Base
|
|
12
|
+
vendor :radware
|
|
13
|
+
# 声明菜单式 CLI 的命令、提示、超时、诊断和确认规则。
|
|
14
|
+
profile do
|
|
15
|
+
running_config_strategy Net::Connector::RunningConfig::Rendered
|
|
16
|
+
tftp_strategy Net::Connector::Radware::TftpBackup
|
|
17
|
+
topology_strategy Net::Connector::Radware::Topology
|
|
18
|
+
commands do
|
|
19
|
+
running_config "/cfg/dump"
|
|
20
|
+
save_config "/cfg/save"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
prompts do
|
|
24
|
+
login(/(?:\A|(?<=[\r\n]))(?:>>[^\r\n]*|[^\r\n]+[>#])\s*\z/)
|
|
25
|
+
command(/(?:\A|(?<=[\r\n]))(?:>>[^\r\n]*|[^\r\n]+#)\s*\z/)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
errors do
|
|
29
|
+
command(/^[ \t]*(?:Unknown|Invalid|Error)\s+command\b/i)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
command_timeout 30
|
|
33
|
+
|
|
34
|
+
interactions do
|
|
35
|
+
confirm %r{Confirm\s+Sync\s+to\s+Peer\s+\[y/n\]:\s*\z}i, response: "y\n"
|
|
36
|
+
confirm %r{Synchronize\s+configuration\s+changes\?\s+\[y/n\]:\s*\z}i, response: "y\n"
|
|
37
|
+
confirm %r{Confirm\s+saving\s+to\s+FLASH\s+\[y/n\]:\s*\z}i, response: "y\n"
|
|
38
|
+
confirm %r{Confirm\s+operation\s+without\s+applying\s+changes\s+
|
|
39
|
+
\(leave\s+them\s+pending\)\s+\[y/n\]:\s*\z}ix, response: "y\n"
|
|
40
|
+
confirm %r{Confirm\s+operation\s+without\s+saving\s+changes\s+
|
|
41
|
+
\(leave\s+them\s+pending\)\s+\[y/n\]:?\s*\z}ix, response: "y\n"
|
|
42
|
+
confirm(/Confirm\s+seeing\s+above\s+note\s+\[y\]:\s*\z/i, response: "y\n")
|
|
43
|
+
confirm(/Please\s+enter\s+y\s+to\s+perform\s+the\s+action,\s+n\s+to\s+skip\s+it:\s*\z/i,
|
|
44
|
+
response: "y\n"
|
|
45
|
+
)
|
|
46
|
+
confirm %r{Display\s+private\s+keys\?\s+\[y/n\]:\s*\z}i, response: "n\n"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# 连接有效期间返回登录横幅中的 HA 状态。
|
|
51
|
+
def ha_state = connected? ? @ha_state : nil
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
# 从登录横幅提取 HA 状态并冻结,供连接期间查询。
|
|
56
|
+
def after_login(_session, response)
|
|
57
|
+
@ha_state = response.raw[/^\s*HA\s+State:\s*(\S+)/i, 1]&.freeze
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "connector/version"
|
|
4
|
+
require_relative "connector/device/base"
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module Connector
|
|
8
|
+
VENDORS = {
|
|
9
|
+
h3c: "H3c",
|
|
10
|
+
h3c_wireless: "H3cWireless",
|
|
11
|
+
cisco_ios: "CiscoIos",
|
|
12
|
+
cisco_nxos: "CiscoNxos",
|
|
13
|
+
radware: "Radware",
|
|
14
|
+
palo_alto: "PaloAlto",
|
|
15
|
+
huawei: "Huawei",
|
|
16
|
+
hillstone: "Hillstone"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
ALIASES = {
|
|
20
|
+
"cisco_n9k" => :cisco_nxos,
|
|
21
|
+
"paloalto" => :palo_alto
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# 返回当前支持的厂商标识集合。
|
|
25
|
+
def self.vendors = VENDORS.keys
|
|
26
|
+
|
|
27
|
+
# 根据厂商标识查找连接器类。
|
|
28
|
+
def self.vendor_class(vendor)
|
|
29
|
+
key = vendor.to_s.downcase.tr(" -", "__")
|
|
30
|
+
key = ALIASES.fetch(key, key.to_sym)
|
|
31
|
+
name = VENDORS.fetch(key) { raise ArgumentError, "unsupported vendor: #{vendor.inspect}" }
|
|
32
|
+
require_relative "connector/vendor/#{key}"
|
|
33
|
+
const_get(name, false).const_get(:Connector, false)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 根据厂商标识和连接参数创建设备对象。
|
|
37
|
+
def self.build(vendor, **settings)
|
|
38
|
+
vendor_class(vendor).new(**settings)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 在代码块内连接设备并确保会话关闭。
|
|
42
|
+
def self.open(vendor, **settings)
|
|
43
|
+
raise ArgumentError, "open requires a block" unless block_given?
|
|
44
|
+
|
|
45
|
+
vendor_class(vendor).open(**settings) { |device| yield device }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|