net-connector 0.4.2 → 0.6.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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -1
  3. data/README.md +92 -20
  4. data/docs/VERIFICATION.md +17 -7
  5. data/docs/architecture.md +102 -64
  6. data/examples/netdisco_database.yml +16 -0
  7. data/lib/net/connector/device/base.rb +27 -107
  8. data/lib/net/connector/device/local_backup.rb +96 -0
  9. data/lib/net/connector/device/profile.rb +8 -3
  10. data/lib/net/connector/device/running_config/strategy.rb +1 -1
  11. data/lib/net/connector/device/running_config.rb +44 -5
  12. data/lib/net/connector/device/save_config.rb +21 -0
  13. data/lib/net/connector/device/tftp/file_upload.rb +53 -0
  14. data/lib/net/connector/{operations/tftp_receipt.rb → device/tftp/receipt.rb} +18 -20
  15. data/lib/net/connector/device/tftp/strategy.rb +67 -0
  16. data/lib/net/connector/device/tftp/target.rb +63 -0
  17. data/lib/net/connector/device/tftp.rb +117 -0
  18. data/lib/net/connector/device/topology/immediate_strategy.rb +40 -0
  19. data/lib/net/connector/device/topology/interface_description.rb +46 -0
  20. data/lib/net/connector/device/topology/interface_name.rb +58 -0
  21. data/lib/net/connector/device/topology/strategy.rb +75 -0
  22. data/lib/net/connector/device/topology.rb +277 -0
  23. data/lib/net/connector/engine/authentication.rb +2 -2
  24. data/lib/net/connector/engine/configuration.rb +1 -1
  25. data/lib/net/connector/engine/dialogue.rb +21 -21
  26. data/lib/net/connector/engine/error_metadata.rb +50 -0
  27. data/lib/net/connector/engine/errors.rb +15 -1
  28. data/lib/net/connector/engine/execution.rb +17 -10
  29. data/lib/net/connector/engine/log/event.rb +66 -0
  30. data/lib/net/connector/engine/log/formatter.rb +16 -0
  31. data/lib/net/connector/engine/log/messages.rb +52 -0
  32. data/lib/net/connector/engine/log/stream.rb +56 -0
  33. data/lib/net/connector/engine/log.rb +105 -84
  34. data/lib/net/connector/engine/session.rb +83 -42
  35. data/lib/net/connector/engine/terminal_text.rb +33 -0
  36. data/lib/net/connector/engine/transport.rb +2 -2
  37. data/lib/net/connector/netdisco/batch.rb +9 -45
  38. data/lib/net/connector/netdisco/cli.rb +17 -10
  39. data/lib/net/connector/netdisco/client.rb +4 -3
  40. data/lib/net/connector/netdisco/config_file.rb +6 -3
  41. data/lib/net/connector/netdisco/database_client.rb +197 -0
  42. data/lib/net/connector/netdisco/device.rb +4 -5
  43. data/lib/net/connector/netdisco/diagnostic.rb +9 -39
  44. data/lib/net/connector/netdisco/fleet.rb +26 -39
  45. data/lib/net/connector/netdisco/report.rb +37 -16
  46. data/lib/net/connector/netdisco/result_store.rb +2 -2
  47. data/lib/net/connector/netdisco/settings.rb +26 -9
  48. data/lib/net/connector/netdisco.rb +1 -0
  49. data/lib/net/connector/{operations → storage}/backup_lock.rb +2 -2
  50. data/lib/net/connector/{operations → storage}/private_file.rb +2 -7
  51. data/lib/net/connector/{operations → storage}/safe_file.rb +1 -1
  52. data/lib/net/connector/{operations → storage}/saved_config.rb +10 -35
  53. data/lib/net/connector/storage.rb +15 -0
  54. data/lib/net/connector/textfsm.rb +72 -0
  55. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +3 -3
  56. data/lib/net/connector/vendor/cisco_ios/topology.rb +5 -5
  57. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +3 -3
  58. data/lib/net/connector/vendor/h3c/tftp_backup.rb +4 -4
  59. data/lib/net/connector/vendor/h3c/topology.rb +5 -5
  60. data/lib/net/connector/vendor/h3c.rb +3 -3
  61. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +3 -3
  62. data/lib/net/connector/vendor/hillstone/topology.rb +5 -5
  63. data/lib/net/connector/vendor/huawei/tftp_backup.rb +3 -3
  64. data/lib/net/connector/vendor/palo_alto/running_config.rb +1 -1
  65. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +3 -3
  66. data/lib/net/connector/vendor/palo_alto/topology.rb +3 -6
  67. data/lib/net/connector/vendor/radware/tftp_backup.rb +3 -3
  68. data/lib/net/connector/vendor/radware/topology.rb +2 -2
  69. data/lib/net/connector/version.rb +1 -1
  70. data/lib/net/connector.rb +1 -6
  71. metadata +47 -39
  72. data/lib/net/connector/device/interface_description.rb +0 -44
  73. data/lib/net/connector/device/interface_name.rb +0 -56
  74. data/lib/net/connector/engine/base.rb +0 -4
  75. data/lib/net/connector/engine/log_messages.rb +0 -47
  76. data/lib/net/connector/engine/profile.rb +0 -4
  77. data/lib/net/connector/engine.rb +0 -4
  78. data/lib/net/connector/operations/local_backup.rb +0 -89
  79. data/lib/net/connector/operations/parse_output.rb +0 -77
  80. data/lib/net/connector/operations/running_config/cisco.rb +0 -14
  81. data/lib/net/connector/operations/running_config/hillstone.rb +0 -12
  82. data/lib/net/connector/operations/running_config/palo_alto.rb +0 -12
  83. data/lib/net/connector/operations/running_config/strategy.rb +0 -3
  84. data/lib/net/connector/operations/running_config.rb +0 -16
  85. data/lib/net/connector/operations/saved_config/legacy_index.rb +0 -109
  86. data/lib/net/connector/operations/tftp/cisco_ios.rb +0 -11
  87. data/lib/net/connector/operations/tftp/cisco_nxos.rb +0 -11
  88. data/lib/net/connector/operations/tftp/file_upload.rb +0 -55
  89. data/lib/net/connector/operations/tftp/h3c.rb +0 -11
  90. data/lib/net/connector/operations/tftp/hillstone.rb +0 -11
  91. data/lib/net/connector/operations/tftp/huawei.rb +0 -11
  92. data/lib/net/connector/operations/tftp/palo_alto.rb +0 -11
  93. data/lib/net/connector/operations/tftp/radware.rb +0 -11
  94. data/lib/net/connector/operations/tftp/strategy.rb +0 -75
  95. data/lib/net/connector/operations/tftp_backup.rb +0 -183
  96. data/lib/net/connector/operations/topology/cisco.rb +0 -11
  97. data/lib/net/connector/operations/topology/h3c.rb +0 -11
  98. data/lib/net/connector/operations/topology/hillstone.rb +0 -11
  99. data/lib/net/connector/operations/topology/immediate_strategy.rb +0 -45
  100. data/lib/net/connector/operations/topology/palo_alto.rb +0 -11
  101. data/lib/net/connector/operations/topology/radware.rb +0 -11
  102. data/lib/net/connector/operations/topology/strategy.rb +0 -87
  103. data/lib/net/connector/operations/topology.rb +0 -261
  104. data/lib/net/connector/operations.rb +0 -26
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../operations/topology/strategy"
3
+ require_relative "../../device/topology/strategy"
4
4
 
5
5
  module Net
6
6
  module Connector
7
7
  module Radware
8
- class Topology < Operations::Topology::Strategy
8
+ class Topology < Topology::Strategy
9
9
  # Alteon 只支持读取端口描述,不提供邻居发现或自动改写。
10
10
  def self.supports?(capability) = capability == :interface_descriptions
11
11
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Net
4
4
  module Connector
5
- VERSION = "0.4.2"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
data/lib/net/connector.rb CHANGED
@@ -16,18 +16,13 @@ module Net
16
16
  hillstone: "Hillstone"
17
17
  }.freeze
18
18
 
19
- ALIASES = {
20
- "cisco_n9k" => :cisco_nxos,
21
- "paloalto" => :palo_alto
22
- }.freeze
23
-
24
19
  # 返回当前支持的厂商标识集合。
25
20
  def self.vendors = VENDORS.keys
26
21
 
27
22
  # 根据厂商标识查找连接器类。
28
23
  def self.vendor_class(vendor)
29
24
  key = vendor.to_s.downcase.tr(" -", "__")
30
- key = ALIASES.fetch(key, key.to_sym)
25
+ key = key.to_sym
31
26
  name = VENDORS.fetch(key) { raise ArgumentError, "unsupported vendor: #{vendor.inspect}" }
32
27
  require_relative "connector/vendor/#{key}"
33
28
  const_get(name, false).const_get(:Connector, false)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-connector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - net-connector contributors
@@ -37,6 +37,26 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 0.2.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: pg
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.5'
47
+ - - "<"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '1.5'
57
+ - - "<"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
40
60
  - !ruby/object:Gem::Dependency
41
61
  name: digest
42
62
  requirement: !ruby/object:Gem::Requirement
@@ -407,38 +427,52 @@ files:
407
427
  - docs/RELEASING.md
408
428
  - docs/VERIFICATION.md
409
429
  - docs/architecture.md
430
+ - examples/netdisco_database.yml
410
431
  - exe/net-connector-backup
411
432
  - lib/net-connector.rb
412
433
  - lib/net/connector.rb
413
434
  - lib/net/connector/device/base.rb
414
- - lib/net/connector/device/interface_description.rb
415
- - lib/net/connector/device/interface_name.rb
435
+ - lib/net/connector/device/local_backup.rb
416
436
  - lib/net/connector/device/profile.rb
417
437
  - lib/net/connector/device/profile/builder.rb
418
438
  - lib/net/connector/device/running_config.rb
419
439
  - lib/net/connector/device/running_config/strategy.rb
420
- - lib/net/connector/engine.rb
440
+ - lib/net/connector/device/save_config.rb
441
+ - lib/net/connector/device/tftp.rb
442
+ - lib/net/connector/device/tftp/file_upload.rb
443
+ - lib/net/connector/device/tftp/receipt.rb
444
+ - lib/net/connector/device/tftp/strategy.rb
445
+ - lib/net/connector/device/tftp/target.rb
446
+ - lib/net/connector/device/topology.rb
447
+ - lib/net/connector/device/topology/immediate_strategy.rb
448
+ - lib/net/connector/device/topology/interface_description.rb
449
+ - lib/net/connector/device/topology/interface_name.rb
450
+ - lib/net/connector/device/topology/strategy.rb
421
451
  - lib/net/connector/engine/authentication.rb
422
- - lib/net/connector/engine/base.rb
423
452
  - lib/net/connector/engine/command.rb
424
453
  - lib/net/connector/engine/configuration.rb
425
454
  - lib/net/connector/engine/core.rb
426
455
  - lib/net/connector/engine/dialogue.rb
456
+ - lib/net/connector/engine/error_metadata.rb
427
457
  - lib/net/connector/engine/errors.rb
428
458
  - lib/net/connector/engine/execution.rb
429
459
  - lib/net/connector/engine/log.rb
430
- - lib/net/connector/engine/log_messages.rb
431
- - lib/net/connector/engine/profile.rb
460
+ - lib/net/connector/engine/log/event.rb
461
+ - lib/net/connector/engine/log/formatter.rb
462
+ - lib/net/connector/engine/log/messages.rb
463
+ - lib/net/connector/engine/log/stream.rb
432
464
  - lib/net/connector/engine/recovery.rb
433
465
  - lib/net/connector/engine/result.rb
434
466
  - lib/net/connector/engine/session.rb
435
467
  - lib/net/connector/engine/terminal_renderer.rb
468
+ - lib/net/connector/engine/terminal_text.rb
436
469
  - lib/net/connector/engine/transport.rb
437
470
  - lib/net/connector/netdisco.rb
438
471
  - lib/net/connector/netdisco/batch.rb
439
472
  - lib/net/connector/netdisco/cli.rb
440
473
  - lib/net/connector/netdisco/client.rb
441
474
  - lib/net/connector/netdisco/config_file.rb
475
+ - lib/net/connector/netdisco/database_client.rb
442
476
  - lib/net/connector/netdisco/device.rb
443
477
  - lib/net/connector/netdisco/diagnostic.rb
444
478
  - lib/net/connector/netdisco/fleet.rb
@@ -450,38 +484,11 @@ files:
450
484
  - lib/net/connector/netdisco/rules.rb
451
485
  - lib/net/connector/netdisco/settings.rb
452
486
  - lib/net/connector/netdisco/worker.rb
453
- - lib/net/connector/operations.rb
454
- - lib/net/connector/operations/backup_lock.rb
455
- - lib/net/connector/operations/local_backup.rb
456
- - lib/net/connector/operations/parse_output.rb
457
- - lib/net/connector/operations/private_file.rb
458
- - lib/net/connector/operations/running_config.rb
459
- - lib/net/connector/operations/running_config/cisco.rb
460
- - lib/net/connector/operations/running_config/hillstone.rb
461
- - lib/net/connector/operations/running_config/palo_alto.rb
462
- - lib/net/connector/operations/running_config/strategy.rb
463
- - lib/net/connector/operations/safe_file.rb
464
- - lib/net/connector/operations/saved_config.rb
465
- - lib/net/connector/operations/saved_config/legacy_index.rb
466
- - lib/net/connector/operations/tftp/cisco_ios.rb
467
- - lib/net/connector/operations/tftp/cisco_nxos.rb
468
- - lib/net/connector/operations/tftp/file_upload.rb
469
- - lib/net/connector/operations/tftp/h3c.rb
470
- - lib/net/connector/operations/tftp/hillstone.rb
471
- - lib/net/connector/operations/tftp/huawei.rb
472
- - lib/net/connector/operations/tftp/palo_alto.rb
473
- - lib/net/connector/operations/tftp/radware.rb
474
- - lib/net/connector/operations/tftp/strategy.rb
475
- - lib/net/connector/operations/tftp_backup.rb
476
- - lib/net/connector/operations/tftp_receipt.rb
477
- - lib/net/connector/operations/topology.rb
478
- - lib/net/connector/operations/topology/cisco.rb
479
- - lib/net/connector/operations/topology/h3c.rb
480
- - lib/net/connector/operations/topology/hillstone.rb
481
- - lib/net/connector/operations/topology/immediate_strategy.rb
482
- - lib/net/connector/operations/topology/palo_alto.rb
483
- - lib/net/connector/operations/topology/radware.rb
484
- - lib/net/connector/operations/topology/strategy.rb
487
+ - lib/net/connector/storage.rb
488
+ - lib/net/connector/storage/backup_lock.rb
489
+ - lib/net/connector/storage/private_file.rb
490
+ - lib/net/connector/storage/safe_file.rb
491
+ - lib/net/connector/storage/saved_config.rb
485
492
  - lib/net/connector/templates/cisco_cdp_neighbors_detail.textfsm
486
493
  - lib/net/connector/templates/cisco_ios_running_config_interfaces.textfsm
487
494
  - lib/net/connector/templates/cisco_ios_show_ip_interface_brief.textfsm
@@ -494,6 +501,7 @@ files:
494
501
  - lib/net/connector/templates/palo_alto_interface_descriptions.textfsm
495
502
  - lib/net/connector/templates/palo_alto_lldp_neighbors.textfsm
496
503
  - lib/net/connector/templates/radware_port_names.textfsm
504
+ - lib/net/connector/textfsm.rb
497
505
  - lib/net/connector/vendor/cisco_ios.rb
498
506
  - lib/net/connector/vendor/cisco_ios/running_config.rb
499
507
  - lib/net/connector/vendor/cisco_ios/tftp_backup.rb
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "interface_name"
4
-
5
- module Net
6
- module Connector
7
- # 描述文本与常见接口视图命令的纯构造方法,不连接或修改设备。
8
- module InterfaceDescription
9
- # 将邻居名称和接口拼成默认描述;只缩写邻居接口,保留原始大小写。
10
- def self.format(neighbor, abbreviate: true, lowercase: false)
11
- port = if abbreviate
12
- InterfaceName.short(neighbor.neighbor_interface, lowercase: lowercase)
13
- else
14
- lowercase ? neighbor.neighbor_interface.downcase : neighbor.neighbor_interface
15
- end
16
- validate!("To #{neighbor.neighbor_name} #{port}")
17
- end
18
-
19
- # 生成进入接口、设置描述和退出视图的命令,不执行设备操作。
20
- def self.commands(interface:, description:, leave: "exit")
21
- validate_interface!(interface)
22
- validate!(description)
23
- raise ArgumentError, "interface exit must be exit or quit" unless %w[exit quit].include?(leave)
24
-
25
- ["interface #{interface}", "description #{description}", leave]
26
- end
27
-
28
- # 共享的输入约束同样适用于 PAN-OS 的独立命令语法。
29
- def self.validate!(value)
30
- return value if value.is_a?(String) && value.bytesize.between?(1, 80) &&
31
- value.match?(/\A[\p{Alnum}_.: \/-]+\z/u)
32
-
33
- raise ArgumentError, "description must be 1-80 bytes of plain interface text"
34
- end
35
-
36
- # 拒绝可能把描述命令拆成多条命令的不安全接口名。
37
- def self.validate_interface!(value)
38
- return value if value.is_a?(String) && value.match?(/\A[A-Za-z][A-Za-z0-9.\/-]*\z/)
39
-
40
- raise ArgumentError, "neighbor interface name is unsafe"
41
- end
42
- end
43
- end
44
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Net
4
- module Connector
5
- # 接口身份匹配与描述展示共用名称规则;展示简称不能直接用作下发命令。
6
- module InterfaceName
7
- RULES = [
8
- [/\A(?:xge|ten-gigabitethernet|tengigabitethernet|te)(?=\d)/i, "tengigabitethernet", "Te"],
9
- [/\A(?:gigabitethernet|ge|gi)(?=\d)/i, "gigabitethernet", "Gi"],
10
- [/\A(?:fastethernet|fa)(?=\d)/i, "fastethernet", "Fa"],
11
- [/\A(?:ethernet|eth)(?=\d)/i, "ethernet", "Eth"],
12
- [/\A(?:port-channel|po)(?=\d)/i, "port-channel", "Po"]
13
- ].map(&:freeze).freeze
14
-
15
- # 用于本机邻居表与配置的关联;保留已有证据键的拼写契约。
16
- def self.key(name)
17
- rule = RULES.find { |pattern, _key, _short| pattern.match?(name) }
18
- rule ? name.sub(rule.first, rule[1]).downcase : name.downcase
19
- end
20
-
21
- # 仅缩短已识别的前缀,保留端口、子接口编号及未知命名。
22
- # 默认延续输入的大写、小写或首字母大写形式;统一小写由调用方选择。
23
- def self.short(name, lowercase: false)
24
- rule = RULES.find { |pattern, _key, _short| pattern.match?(name) }
25
- formatted = if rule
26
- name.sub(rule.first) do |prefix|
27
- abbreviation = rule.last
28
- if prefix == prefix.downcase
29
- abbreviation.downcase
30
- elsif prefix == prefix.upcase
31
- abbreviation.upcase
32
- elsif prefix.casecmp?(abbreviation)
33
- prefix
34
- else
35
- abbreviation
36
- end
37
- end
38
- else
39
- name.dup
40
- end
41
- lowercase ? formatted.downcase : formatted
42
- end
43
-
44
- # 保留现有配置视图展开规则;厂商策略可覆盖不适用的名称。
45
- def self.configuration(name)
46
- name.sub(/\AXGE(?=\d)/i, "Ten-GigabitEthernet")
47
- .sub(/\AGE(?=\d)/i, "GigabitEthernet")
48
- .sub(/\AGi(?=\d)/i, "GigabitEthernet")
49
- .sub(/\ATe(?=\d)/i, "TenGigabitEthernet")
50
- .sub(/\AFa(?=\d)/i, "FastEthernet")
51
- .sub(/\AEth(?=\d)/i, "Ethernet")
52
- .sub(/\APo(?=\d)/i, "port-channel")
53
- end
54
- end
55
- end
56
- end
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # 旧加载路径保留为转发入口;设备定义位于 device/。
4
- require_relative "../device/base"
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Net
4
- module Connector
5
- # 独立于日志目标的人类可读事件文案。
6
- module LogMessages
7
- module_function
8
-
9
- # 将结构化会话事件转换为便于人工阅读的中文日志。
10
- def format(name, fields)
11
- case name
12
- when "connect"
13
- "开始连接 #{fields[:host]}(#{fields[:protocol].to_s.upcase},账号 #{fields[:username]})"
14
- when "connect_failed"
15
- "连接失败:#{fields[:message]}(#{fields[:error]})"
16
- when "login_start"
17
- "等待设备登录提示"
18
- when "login_output"
19
- "登录过程回显(已脱敏):"
20
- when "login_complete"
21
- fields[:status] == "ok" ? "登录成功,设备提示符:#{fields[:prompt]}" :
22
- "登录失败:#{fields[:message]}(#{fields[:error]})"
23
- when "command_start"
24
- "下发命令:#{fields[:text]}"
25
- when "device_output"
26
- "设备回显:"
27
- when "command_complete"
28
- fields[:status] == "response_received" ? "命令回显结束,已收到设备提示符" :
29
- "命令执行失败:#{fields[:message]}(#{fields[:error]})"
30
- when "command_detail"
31
- "命令耗时 #{fields[:duration_ms]} 毫秒,收到 #{fields[:response_bytes]} 字节回显"
32
- when "tftp_backup"
33
- case fields[:status]
34
- when "reported_uploaded"
35
- "TFTP 备份:设备报告上传成功,目标 #{fields[:server]}/#{fields[:path]};服务器文件尚未核验"
36
- when "transfer_failed"
37
- "TFTP 备份失败:设备报告传输失败,服务器 #{fields[:server]}"
38
- else
39
- "TFTP 备份未确认:设备未报告上传成功,服务器 #{fields[:server]}"
40
- end
41
- else
42
- name.to_s
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # 旧加载路径保留为转发入口;设备定义位于 device/。
4
- require_relative "../device/profile"
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # 保留原设备门面入口;只需要会话执行层时加载 engine/core。
4
- require_relative "device/base"
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require_relative "private_file"
5
- require_relative "backup_lock"
6
- require_relative "safe_file"
7
-
8
- module Net
9
- module Connector
10
- Backup = Data.define(:path, :bytes, :sha256, :collected_at, :change, :previous_sha256) do
11
- # 保存配置备份的路径、摘要和变更状态。
12
- def initialize(path:, bytes:, sha256:, collected_at:, change: nil, previous_sha256: nil)
13
- super
14
- end
15
-
16
- # 判断本次采集是否产生新的配置内容。
17
- def changed? = [:created, :changed].include?(change)
18
- end
19
-
20
- # 只携带库自身的提交回执和配置元数据,不携带正文或原始文件系统异常。
21
- class BackupPersistenceError < Error
22
- attr_reader :backup, :receipt, :underlying_type
23
-
24
- def initialize(backup:, write_error:, host: nil)
25
- unless backup.instance_of?(Backup) && Operations::PrivateFile.receipt_error?(write_error) &&
26
- write_error.receipt.committed? && write_error.receipt.path == backup.path
27
- raise ArgumentError, "backup persistence error requires a matching committed backup receipt"
28
- end
29
-
30
- @backup, @receipt = backup, write_error.receipt
31
- @underlying_type = write_error.underlying_type
32
- code = if write_error.instance_of?(Operations::PrivateFile::DirectorySyncUnsupported)
33
- :backup_durability_unsupported
34
- elsif receipt.durable?
35
- :backup_finalize_failed
36
- else
37
- :backup_persistence_unconfirmed
38
- end
39
- super(write_error.message, code: code, host: host, phase: :backup)
40
- end
41
- end
42
-
43
- module Operations
44
- class LocalBackup
45
- # 保存执行配置采集的设备对象。
46
- def initialize(device) = @device = device
47
-
48
- # 采集配置、识别变更并原子写入私有文件。
49
- def call(path:, lock_timeout: 0)
50
- host = @device.host if @device.respond_to?(:host)
51
- BackupLock.synchronize(path, timeout: lock_timeout, host: host) { |lock| collect_and_write(lock.path) }
52
- end
53
-
54
- private
55
-
56
- def collect_and_write(destination)
57
- contents = @device.running_config.value!
58
- digest = Digest::SHA256.hexdigest(contents)
59
- previous = SafeFile.fingerprint(destination, missing: true, replace_symlink: true)
60
- previous_digest = previous&.sha256
61
- change = if previous_digest.nil?
62
- :created
63
- elsif previous_digest == digest
64
- :unchanged
65
- else
66
- :changed
67
- end
68
-
69
- backup = Backup.new(path: destination, bytes: contents.bytesize,
70
- sha256: digest, collected_at: Time.now.utc, change: change,
71
- previous_sha256: previous_digest)
72
- if change != :unchanged || previous.mode != 0o600 || !SafeFile.same_entry?(destination, previous)
73
- write_backup(backup, contents)
74
- end
75
- backup
76
- end
77
-
78
- def write_backup(backup, contents)
79
- PrivateFile.write_receipt(backup.path, contents)
80
- rescue PrivateFile::WriteError => error
81
- raise unless PrivateFile.receipt_error?(error) && error.receipt.committed?
82
-
83
- host = @device.host if @device.respond_to?(:host)
84
- raise BackupPersistenceError.new(backup: backup, write_error: error, host: host), cause: nil
85
- end
86
- end
87
- end
88
- end
89
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "textfsm"
4
- require_relative "../engine/errors"
5
- require_relative "../engine/terminal_renderer"
6
-
7
- module Net
8
- module Connector
9
- module Operations
10
- # 将设备命令回显或配置文本按 TextFSM 模板转换为结构化记录。
11
- class ParseOutput
12
- DEFAULT_TEMPLATE_DIR = File.expand_path("../templates", __dir__).freeze
13
-
14
- # 配置模板目录和 Netmiko 风格的命令索引文件。
15
- def initialize(template_dir: nil, index: "index")
16
- template_dir ||= DEFAULT_TEMPLATE_DIR
17
- raise ArgumentError, "template_dir must be a nonempty String" unless template_dir.is_a?(String) && !template_dir.empty?
18
- raise ArgumentError, "index must be a nonempty String" unless index.is_a?(String) && !index.empty?
19
-
20
- @template_dir = File.expand_path(template_dir)
21
- @index = index
22
- end
23
-
24
- # 解析按 UTF-8 字节处理副本,先验证原文,避免回车或转义序列隐藏非法字节。
25
- # 拓扑选模板和计数也使用这份副本,但仍保留未经终端渲染的完整证据。
26
- def utf8(text, host: nil)
27
- raise ArgumentError, "text must be a String" unless text.is_a?(String)
28
-
29
- text = text.b.force_encoding(Encoding::UTF_8)
30
- invalid_encoding!(host) unless text.valid_encoding?
31
- text
32
- end
33
-
34
- # 显式模板按路径读取;未指定时按厂商和命令从索引选择模板。
35
- def call(text, template: nil, vendor: nil, command: nil, host: nil)
36
- text = TerminalRenderer.render(utf8(text, host: host), strict_utf8: true).force_encoding(Encoding::UTF_8)
37
- if template
38
- raise ArgumentError, "template must be a nonempty String" unless template.is_a?(String) && !template.empty?
39
-
40
- TextFSM::Parser.from_file(File.expand_path(template, @template_dir)).parse_hashes(text)
41
- else
42
- raise ArgumentError, "vendor and command are required for template lookup" unless vendor && command.is_a?(String) && !command.empty?
43
-
44
- attributes = { "Vendor" => vendor.to_s, "Command" => command }
45
- table = TextFSM::CliTable.new(index: @index, template_dir: @template_dir)
46
- unless table.index.match(attributes)
47
- raise ParsingError.new("no TextFSM template matches this vendor and command",
48
- code: :template_missing, host: host, phase: :parse)
49
- end
50
- table.parse(text, attributes: attributes).to_hashes
51
- end
52
- rescue Encoding::InvalidByteSequenceError
53
- invalid_encoding!(host)
54
- rescue TextFSM::IndexError
55
- raise ParsingError.new("TextFSM template index is invalid",
56
- code: :template_invalid, host: host, phase: :parse), cause: nil
57
- rescue TextFSM::TemplateError
58
- raise ParsingError.new("TextFSM template is invalid",
59
- code: :template_invalid, host: host, phase: :parse), cause: nil
60
- rescue TextFSM::ParseError
61
- raise ParsingError.new("TextFSM could not parse the device output",
62
- code: :parse_failed, host: host, phase: :parse), cause: nil
63
- rescue IOError, SystemCallError
64
- raise ParsingError.new("unable to read TextFSM template",
65
- code: :template_unreadable, host: host, phase: :parse), cause: nil
66
- end
67
-
68
- private
69
-
70
- def invalid_encoding!(host)
71
- raise ParsingError.new("device output is not valid UTF-8", code: :invalid_output_encoding,
72
- host: host, phase: :parse), cause: nil
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../running_config"
4
- require_relative "../../vendor/cisco_ios/running_config"
5
- require_relative "../../vendor/cisco_nxos/running_config"
6
-
7
- module Net
8
- module Connector
9
- module Operations
10
- RunningConfig::Cisco = Net::Connector::CiscoIos::RunningConfig
11
- RunningConfig::CiscoNxos = Net::Connector::CiscoNxos::RunningConfig
12
- end
13
- end
14
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../running_config"
4
- require_relative "../../vendor/hillstone/running_config"
5
-
6
- module Net
7
- module Connector
8
- module Operations
9
- RunningConfig::Hillstone = Net::Connector::Hillstone::RunningConfig
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../running_config"
4
- require_relative "../../vendor/palo_alto/running_config"
5
-
6
- module Net
7
- module Connector
8
- module Operations
9
- RunningConfig::PaloAlto = Net::Connector::PaloAlto::RunningConfig
10
- end
11
- end
12
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../running_config"
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../device/running_config"
4
-
5
- module Net
6
- module Connector
7
- module Operations
8
- # 旧常量与新能力共享同一实现,不维护第二套采集流程。
9
- RunningConfig = Net::Connector::RunningConfig
10
- RunningConfig.autoload :Cisco, File.expand_path("running_config/cisco", __dir__)
11
- RunningConfig.autoload :CiscoNxos, File.expand_path("running_config/cisco", __dir__)
12
- RunningConfig.autoload :Hillstone, File.expand_path("running_config/hillstone", __dir__)
13
- RunningConfig.autoload :PaloAlto, File.expand_path("running_config/palo_alto", __dir__)
14
- end
15
- end
16
- end