net-connector 0.12.0 → 0.14.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +1 -1
  4. data/README.zh-CN.md +1 -1
  5. data/docs/VERIFICATION.md +19 -5
  6. data/docs/architecture.md +18 -5
  7. data/docs/netdisco.md +3 -3
  8. data/docs/netdisco.zh-CN.md +7 -7
  9. data/docs/templates.md +13 -7
  10. data/docs/usage.md +2 -2
  11. data/docs/usage.zh-CN.md +2 -2
  12. data/docs/vendors.md +14 -3
  13. data/examples/backup.yml +47 -8
  14. data/examples/inventory.yml +39 -0
  15. data/lib/net/connector/device/profile.rb +1 -1
  16. data/lib/net/connector/device/remote_backup/receipt.rb +4 -0
  17. data/lib/net/connector/device/remote_backup/target.rb +3 -0
  18. data/lib/net/connector/device/tftp/strategy.rb +7 -2
  19. data/lib/net/connector/device/tftp.rb +13 -4
  20. data/lib/net/connector/device/topology/audit.rb +103 -0
  21. data/lib/net/connector/device/topology/change.rb +22 -0
  22. data/lib/net/connector/device/topology/immediate_strategy.rb +3 -9
  23. data/lib/net/connector/device/topology/interface_blocks.rb +18 -0
  24. data/lib/net/connector/device/topology/neighbor.rb +22 -0
  25. data/lib/net/connector/device/topology/plan.rb +39 -0
  26. data/lib/net/connector/device/topology/quoted_text.rb +23 -0
  27. data/lib/net/connector/device/topology/strategy.rb +4 -0
  28. data/lib/net/connector/device/topology.rb +29 -12
  29. data/lib/net/connector/netdisco/address_filter.rb +86 -0
  30. data/lib/net/connector/netdisco/batch.rb +33 -4
  31. data/lib/net/connector/netdisco/cli/backup.rb +80 -25
  32. data/lib/net/connector/netdisco/cli/help.rb +47 -0
  33. data/lib/net/connector/netdisco/cli/options.rb +111 -18
  34. data/lib/net/connector/netdisco/cli/topology.rb +358 -0
  35. data/lib/net/connector/netdisco/config/initializer.rb +146 -0
  36. data/lib/net/connector/netdisco/{config_file.rb → config.rb} +26 -4
  37. data/lib/net/connector/netdisco/device/policy.rb +77 -0
  38. data/lib/net/connector/netdisco/device.rb +14 -3
  39. data/lib/net/connector/netdisco/fleet/options.rb +93 -0
  40. data/lib/net/connector/netdisco/fleet.rb +114 -59
  41. data/lib/net/connector/netdisco/inventory/cache.rb +42 -4
  42. data/lib/net/connector/netdisco/operations/batch.rb +1 -1
  43. data/lib/net/connector/netdisco/operations/outcome.rb +2 -16
  44. data/lib/net/connector/netdisco/operations.rb +20 -38
  45. data/lib/net/connector/netdisco/plan.rb +39 -16
  46. data/lib/net/connector/netdisco/planner.rb +22 -8
  47. data/lib/net/connector/netdisco/progress.rb +45 -2
  48. data/lib/net/connector/netdisco/report.rb +14 -11
  49. data/lib/net/connector/netdisco/rules.rb +34 -17
  50. data/lib/net/connector/netdisco/settings.rb +88 -23
  51. data/lib/net/connector/netdisco/tftp/policy.rb +152 -0
  52. data/lib/net/connector/netdisco/topology/options.rb +48 -0
  53. data/lib/net/connector/netdisco.rb +1 -0
  54. data/lib/net/connector/templates/cisco/show_running_config.textfsm +1 -0
  55. data/lib/net/connector/templates/cisco_nxos/show_running_config.textfsm +1 -0
  56. data/lib/net/connector/templates/fortinet/show_full_configuration.textfsm +21 -0
  57. data/lib/net/connector/templates/h3c/display_current_configuration.textfsm +1 -0
  58. data/lib/net/connector/templates/h3c/{display_lldp_neighbor_information_list_local_first.textfsm → display_lldp_neighbor_information_list.textfsm} +7 -1
  59. data/lib/net/connector/templates/hillstone/show_configuration_running.textfsm +1 -0
  60. data/lib/net/connector/templates/huawei/display_current_configuration.textfsm +13 -0
  61. data/lib/net/connector/templates/huawei/display_lldp_neighbor_brief.textfsm +9 -0
  62. data/lib/net/connector/templates/index +22 -12
  63. data/lib/net/connector/templates/juniper/show_configuration_display_set.textfsm +4 -0
  64. data/lib/net/connector/templates/palo_alto/show.textfsm +1 -0
  65. data/lib/net/connector/templates/radware/cfg_dump.textfsm +5 -3
  66. data/lib/net/connector/vendor/fortinet/topology.rb +24 -1
  67. data/lib/net/connector/vendor/h3c/tftp.rb +30 -0
  68. data/lib/net/connector/vendor/h3c/topology.rb +4 -5
  69. data/lib/net/connector/vendor/huawei/topology.rb +48 -0
  70. data/lib/net/connector/vendor/huawei.rb +2 -0
  71. data/lib/net/connector/vendor/juniper/topology.rb +7 -6
  72. data/lib/net/connector/vendor/palo_alto/topology.rb +1 -7
  73. data/lib/net/connector/vendor/radware/tftp.rb +10 -1
  74. data/lib/net/connector/vendor/radware/topology.rb +2 -0
  75. data/lib/net/connector/version.rb +1 -1
  76. metadata +71 -52
  77. data/examples/inventory_sql.yml +0 -20
  78. data/lib/net/connector/templates/h3c/display_lldp_neighbor_information_list_name_first.textfsm +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3ed9fc4eb5accaa27d54868fab18082ec076db16d0a41c46c52dcf2d700b79c
4
- data.tar.gz: c89082dfd48d20024a07abca986c19320be6a385d78f95f15b01ed5e1a1c68ed
3
+ metadata.gz: d1f1aa8bb2e1ce2a457e99cde7439206c7f4b1c44ccd590db5b6eb330386a39d
4
+ data.tar.gz: 4b29cb8d180a7428225badc8bedc90ac6887d574ad8eccbfd251159141f7bc2f
5
5
  SHA512:
6
- metadata.gz: 7bbb0cd9634b952fd1dd84f9283fa30c008866f6a9494b52191927817514eb5d3662286f890782ef5d9ce3fcbdf540ff7a24d057923ba1ac22a9245b998b6429
7
- data.tar.gz: 4151ff303d38cbf7ff2dad8262d60793e3aef0fc1b0170ab21fc52931a9ca9b74f45a3bcdf7574bfcab06b04ddd6e1949bae422673adb05cf25bac9ad1e9ccfb
6
+ metadata.gz: f4743a08bf81e10e58e6475c33c7537bc87bc68b6f418d9badd3502382e4a35395af00a923923d64349f8499862b50f3ebb85f8d963f421348028fd7c3d7c978
7
+ data.tar.gz: a23c96f292c6f69c56a2de6c0e62684f3d4d9b090d37e68d97315d8e9c3ecf22e7cc008776ca25fd74e0d284dd287031ed0b97431224c5e33e9dcba873c3edbf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.14.0 - 2026-10-18
4
+
5
+ - 不兼容调整:Netdisco 配置与策略入口整理为 `Config`、`Config::Initializer`、`Device::Policy`、`Topology::Options`,原常量和加载路径已移除,直接引用者需更新。
6
+ - 示例入口统一为 `backup.rb`、`backup_tftp.rb` 和 `topology.rb`,单台 TFTP 使用 `backup_tftp.rb --host IP`,数据库示例配置改为 `inventory.yml`,原 `device_tftp.rb`、`review_tftp.rb` 和 `inventory_sql.yml` 已移除。
7
+ - 备份示例支持默认配置、首次运行初始化、常用与完整帮助及清单预览,直接使用当前安装或 Bundler 选定的库。
8
+ - 新增拓扑发现、描述审计和只读规划入口,只有显式 `--apply` 才下发,应用前重验邻居及原描述,修改后回读并保存,部分失败和中断保留已完成结果。
9
+ - 清单支持 CIDR、地址范围、名称与厂商筛选、排除和重复参数,新增只读缓存模式,离线预览不连接设备或创建批次目录。
10
+ - TFTP 支持逐设备服务器、源接口、源文件与 VRF 设置,核验目录按服务器绑定,修复仅配置服务器目录映射时将同服务器 PAN-OS 设备误判为文件名冲突的问题。
11
+ - 示例的公开配置按 CLI、YAML、环境默认值依次取值,宿主可通过 `load_env: true` 启用相同规则,正式 CLI 保留原有优先级。
12
+ - 运行时依赖解除版本约束,由宿主应用选择并锁定兼容版本。
13
+
14
+ ## 0.13.0 - 2026-10-18
15
+
16
+ - 不兼容调整:H3C 的 LLDP 列序变体合并为 `h3c/display_lldp_neighbor_information_list.textfsm`,显式引用旧模板路径的调用方需要更新。
17
+ - Huawei VRP 增加 LLDP 邻居和接口描述读取,FortiOS 增加当前视图的接口描述读取,Junos 分别返回物理接口与逻辑单元描述。
18
+ - 补齐各平台配置解析索引,修复 H3C 本地接口首列时的字段错位,并要求厂商名和命令完整匹配,避免选择错误模板。
19
+ - 接口描述缺少值、引号未闭合或配置作用域不明时拒绝解析,Radware 端口名称中的转义引号可正确还原。
20
+ - 拓扑计划和备份批次固定各自拥有的嵌套数据,调用方修改原始容器不再改变已生成结果,更新需通过 `with` 创建新快照。
21
+ - 批量报告复用状态计数,减少重复统计时对设备结果的遍历,未知状态仍不能满足成功策略。
22
+ - 修复 Ruby 3.2 下远程备份目标及回执的 `with` 绕过校验的问题,拒绝非法协议、路径及没有核验证据的完成声明。
23
+
3
24
  ## 0.12.0 - 2026-10-17
4
25
 
5
26
  - 不兼容调整:模块命名空间和加载路径按领域重组,厂商类改为 `Vendor::CiscoIos` 等直接入口,清单与报告存储分别使用 `inventory:`、`report_store:` 注入,旧名称及兼容别名已移除。
data/README.md CHANGED
@@ -41,7 +41,7 @@ net-backup --config config.yml --plan
41
41
  net-backup --config config.yml
42
42
  ```
43
43
 
44
- Start with [backup.yml](examples/backup.yml) or [inventory_sql.yml](examples/inventory_sql.yml). Configure inventory and device credentials in the environment; YAML contains non-sensitive settings. See the [Netdisco guide](docs/netdisco.md) for HTTP/PostgreSQL inventories, success policies, TFTP verification, and batch examples.
44
+ Start with [backup.yml](examples/backup.yml) or [inventory.yml](examples/inventory.yml). Configure inventory and device credentials in the environment; YAML contains non-sensitive settings. See the [Netdisco guide](docs/netdisco.md) for HTTP/PostgreSQL inventories, success policies, TFTP verification, and batch examples.
45
45
 
46
46
  ## Documentation
47
47
 
data/README.zh-CN.md CHANGED
@@ -41,7 +41,7 @@ net-backup --config config.yml --plan
41
41
  net-backup --config config.yml
42
42
  ```
43
43
 
44
- 从 [backup.yml](examples/backup.yml) 或 [inventory_sql.yml](examples/inventory_sql.yml) 开始配置。清单与设备凭据放在环境变量中,YAML 只保存非敏感设置。HTTP/PostgreSQL 清单、成功策略、TFTP 核验和批量示例见 [Netdisco 指南](docs/netdisco.zh-CN.md)。
44
+ 从 [backup.yml](examples/backup.yml) 或 [inventory.yml](examples/inventory.yml) 开始配置。清单与设备凭据放在环境变量中,YAML 只保存非敏感设置。HTTP/PostgreSQL 清单、成功策略、TFTP 核验和批量示例见 [Netdisco 指南](docs/netdisco.zh-CN.md)。
45
45
 
46
46
  ## 文档
47
47
 
data/docs/VERIFICATION.md CHANGED
@@ -155,7 +155,7 @@ SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/
155
155
 
156
156
  运行依赖写在 `net-connector.gemspec`,包括直接使用的、可能从 Ruby 默认
157
157
  安装中拆出的标准库 gem。开发工具只写在 Gemfile,不进入运行依赖。
158
- 共享脱敏要求 `expect-pty ~> 0.5.0`,使用已发布的公共 `Expect::Redactor`。
158
+ 共享脱敏使用 expect-pty 已发布的公共 `Expect::Redactor` 接口。
159
159
  开发及隔离安装检查使用正式依赖包,不再需要本地 expect 补丁或联调环境包装脚本。
160
160
  开发用 `parallel` 保持 1.x,以支持 Ruby 3.2。
161
161
 
@@ -163,9 +163,9 @@ SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/
163
163
  分别解析兼容依赖。应用使用者应在自己的应用中提交 lockfile。测试和打包
164
164
  脚本通过隔离安装检查运行依赖,避免依赖开发环境里偶然存在的 gem。
165
165
 
166
- `gemfiles/minimum.gemfile` 固定关键业务依赖下限 expect-pty 0.5.0、textfsm 0.2.0;
166
+ `gemfiles/minimum.gemfile` 固定较早的业务依赖 expect-pty 0.5.0、textfsm 0.2.0;
167
167
  标准库和开发工具仍按主 Gemfile 的兼容范围解析。普通矩阵检查允许版本的正常解析,
168
- 另在 Ubuntu / Ruby 3.2、4.0 检查下限组合的全量测试与隔离安装。本地复现:
168
+ 另在 Ubuntu / Ruby 3.2、4.0 检查这组版本的全量测试与隔离安装。本地复现:
169
169
 
170
170
  ```sh
171
171
  BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle install
@@ -173,8 +173,8 @@ BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle exec rake test package:verify
173
173
  ```
174
174
 
175
175
  这不是所有传递依赖最旧版本的笛卡尔积,也不声称本地 macOS 运行证明远端矩阵通过。
176
- 当前这两个关键依赖的正常解析与下限恰好相同,报告仍记录实际版本,供以后比较。
177
- 开发锁文件均忽略;gemspec 保持兼容范围,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
176
+ 报告记录实际解析版本,便于比较默认组合和旧版本组合。
177
+ 开发锁文件均忽略;gemspec 不限定依赖版本,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
178
178
 
179
179
  gem 只收录库代码、TextFSM 模板、CLI、双语设备/Netdisco 指南、架构/验证/发布文档、双语 README、LICENSE、
180
180
  CHANGELOG、SECURITY 和 CONTRIBUTING。仅两个非敏感 YAML 示例随包分发;测试、示例脚本、发布工具、工作流、本地评审快照、本地配置和备份不进入包。
@@ -246,3 +246,17 @@ TFTP 远端未核验回执以及自定义日志目录;离线复核同时支持
246
246
  源码根目录 `RENAMES.md` 记录批准的改名与兼容边界。完整验收仍运行 `bundle exec rake ci`;
247
247
  临时 PostgreSQL 使用 `bundle exec rake test:postgres`,可通过 `NC_TEST_PG_BINDIR` 指定服务端工具目录。
248
248
  最低 Ruby 3.2 必须在对应解释器中运行 lint/test;RuboCop 的 TargetRubyVersion 和 Ruby 4.0 测试不能替代此项。
249
+
250
+ ## 报告统计基准
251
+
252
+ ```sh
253
+ bundle exec ruby script/benchmarks/report_workload.rb 100000 20
254
+ ```
255
+
256
+ 脚本分别输出批次构造、首次统计及重复统计的单调时钟耗时与对象分配数;合成结果的创建不计入测量。比较两个源码版本时,在同一 Ruby 与主机上各运行至少三次。批次构造会复制结果容器并累计状态,重复统计复用计数;不读取报告的调用方也承担构造成本。TFTP 文件核验与逐设备明细仍有线性成本,此基准不测网络吞吐或现场设备性能。
257
+
258
+ ## 厂商能力与模板一致性
259
+
260
+ `test/capabilities_test.rb` 离线核对全部已注册平台的能力;`test/vendor_topology_parity_test.rb` 覆盖 Huawei LLDP 简表与接口描述、FortiOS 顶层接口描述/嵌套表隔离,以及 Junos 物理接口和逻辑单元描述。`test/textfsm_test.rb` 同时检查 H3C 两种列序在通用命令解析入口中的语义、内置厂商/命令的完整匹配,以及各配置方言的索引入口。完整性拒绝路径覆盖缺失表头、残缺记录、未闭合接口表/引号、VDOM 作用域不明和没有值的 Junos description。
261
+
262
+ 新增样本来自官方结构说明构造的离线会话,尚无设备联机验收;模板解析成功不能证明固件覆盖或候选配置写入安全。
data/docs/architecture.md CHANGED
@@ -247,10 +247,10 @@ Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限
247
247
  | Cisco NX-OS | 是 | 是 | 是 | 是 | 是 | 是 |
248
248
  | Radware Alteon | 是 | 是 | 是 | 否 | 是 | 否 |
249
249
  | PAN-OS | 是 | 否 | 是 | 是 | 是 | 否,候选隔离未验证 |
250
- | 华为 | 是 | 是 | 是 | 否 | 否 | 否 |
250
+ | 华为 VRP | 是 | 是 | 是 | 是,LLDP 简表 | 是 | 否,提交阶段未验证 |
251
251
  | 山石 | 是 | 是 | 是 | 是 | 是 | 是 |
252
252
  | Topsec TopOS | 是 | 否 | 否 | 否 | 否 | 否 |
253
- | Fortinet FortiOS | 是 | 是 | 是 | 是 | 否 | 否 |
253
+ | Fortinet FortiOS | 是 | 是 | 是 | 是 | 是,当前视图顶层接口表 | 否 |
254
254
  | Juniper Junos | 是 | 否 | 否,原生 FTP/SCP | 是 | 是 | 否,候选库提交未开放 |
255
255
 
256
256
  Junos 的 `remote_backup` 通过 `RemoteBackup` 协调与厂商策略导出已提交配置;回执区分设备报告与远端核验。其余平台保持明确的能力声明,见[平台说明](vendors.md)。
@@ -290,7 +290,7 @@ router.supports?(:tftp_backup) # => true,不连接设备
290
290
 
291
291
  配置采集可把 `running_config_strategy` 绑定到 `Net::Connector::RunningConfig::Strategy` 子类。策略实现 `clean`、`result_step`、`prompt_text` 和 `validate_response!`;PAN-OS 候选差异只影响配置采集,直接 `execute_command("show config diff")` 返回该命令输出。静态命令列表留在档案中。`clean_config` 和受保护的 `config_result_step` 钩子可覆盖并调用 `super`。Base 负责通用脚本与锁内回调,不决定配置是否完整。
292
292
 
293
- 拓扑策略通过 `topology_strategy YourStrategy` 绑定。类方法和实例方法 `supports?` 声明三种拓扑能力,其余方法提供命令、模板、输出完整性和接口拼写。Profile 校验完整接口,包括 `validate_descriptions!`、`change_error_code`、`enter_configuration_command`、`leave_configuration_commands`、`verification_commands`、`persistence_commands` 和 `persistence_confirmed?`。只读策略继承默认空阶段;可写策略必须明确每个阶段,不能用合并的收尾命令推断保存边界。即时生效且使用 interface 块的设备可继承 `Topology::ImmediateStrategy`。自定义采集必须执行已列入计划的命令并返回完成步骤;自定义厂商标签通过 `neighbor_template` 提供模板。
293
+ 拓扑策略通过 `topology_strategy YourStrategy` 绑定。类方法和实例方法 `supports?` 声明三种拓扑能力,其余方法提供命令、模板、输出完整性和接口拼写。Profile 校验完整接口,包括 `description_key`、`validate_descriptions!`、`change_error_code`、`enter_configuration_command`、`leave_configuration_commands`、`verification_commands`、`persistence_commands` 和 `persistence_confirmed?`。只读策略继承默认空阶段;可写策略必须明确每个阶段,不能用合并的收尾命令推断保存边界。即时生效且使用 interface 块的设备可继承 `Topology::ImmediateStrategy`。自定义采集必须执行已列入计划的命令并返回完成步骤;自定义厂商标签通过 `neighbor_template` 提供模板。
294
294
 
295
295
  业务操作针对单个连接器构造,并提供 `call`。TFTP 策略只处理厂商传输差异,公共操作统一成功和失败规则。生成的文件名经过 `TftpTarget` 校验及长度限制;带作用域的 IPv6 地址会转为安全 ASCII 标记,特别长的地址使用稳定 SHA-256 标记。原本合法的文件名保留拼写,只有整体过长才缩短清单名称。TFTP 返回值表示设备报告上传完成,服务器文件核对仍由调用方负责。
296
296
 
@@ -326,7 +326,7 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
326
326
 
327
327
  `engine/interaction`、`engine/dialogue`、`engine/response` 分别定义交互规则、对话语法与响应值;读取循环位于 `engine/response_reader`。`engine/errors` 不加载脱敏运行时,`engine/redactor` 负责秘密作用域。Session 明确加载并持有读取器与脱敏器。
328
328
 
329
- Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb` 的预设加载顺序。总入口只组合 CLI 和 CLI::Backup;Batch 通过 autoload 创建 Report,避免两者循环加载。内部清单预算 `Inventory::Budget` 由 Inventory 清单适配器加载。Progress 的终端显示、Fleet 的设备执行、Report 的结果解释和 Storage 的文件持久化保持独立职责。
329
+ Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb` 的预设加载顺序。总入口组合 CLI、CLI::Backup 和 CLI::Topology;Batch 通过 autoload 创建 Report,避免两者循环加载。内部清单预算 `Inventory::Budget` 由 Inventory 清单适配器加载。Progress 的终端显示、Fleet 的设备执行、Report 的结果解释和 Storage 的文件持久化保持独立职责。
330
330
 
331
331
  公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Topology::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
332
332
 
@@ -357,12 +357,15 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
357
357
  | 命令结果 | `Command::Result`,位于 `engine/command/result.rb`;脚本汇总仍为 `Result` |
358
358
  | 安全诊断词表 | `Error::Metadata`,位于 `engine/error/metadata.rb` |
359
359
  | 清单预算与缓存 | `Netdisco::Inventory::Budget/Cache`,位于 `netdisco/inventory/` |
360
+ | 配置解析与初始化 | `Netdisco::Config` 和 `Config::Initializer`,位于 `netdisco/config.rb` 和 `netdisco/config/initializer.rb` |
361
+ | 逐设备策略 | `Netdisco::Device::Policy`,位于 `netdisco/device/policy.rb`,按厂商默认和设备地址选取参数 |
362
+ | 拓扑批次参数 | `Netdisco::Topology::Options`,位于 `netdisco/topology/options.rb`;设备拓扑能力仍为 `Net::Connector::Topology` |
360
363
  | 配置操作结果 | `Netdisco::Operations::Batch/Outcome`,位于 `netdisco/operations/` |
361
364
  | 报告存储 | `Netdisco::Report::Store`,位于 `netdisco/report/store.rb`,通过 `report_store:` 注入 |
362
365
  | 服务器上传证据 | `Netdisco::Tftp::Verifier/Archive`,位于 `netdisco/tftp/` |
363
366
  | 厂商平台 | `Vendor::CiscoIos` 等平台类直接继承 Base 或协议家族类;策略位于平台类下 |
364
367
  | 文件边界 | `Storage::FileReader` 与 `Storage::AtomicFile`,分别负责受控读取与原子替换 |
365
- | 终端应用 | `Netdisco::CLI::Backup/Credentials/Options`,位于 `netdisco/cli/` |
368
+ | 终端应用 | `Netdisco::CLI::Backup/Topology/Credentials/Options`,位于 `netdisco/cli/` |
366
369
 
367
370
  工厂方法用 `build_*` 表明返回新对象;抛错校验使用 `validate_*!`;块作用域使用明确的 `with_*`。单命令、脚本、命令文本构造和完成证据判断分别命名,避免从通用的 execute/check/complete 猜测行为。`legacy_ssh` 表示设备 SSH 协议协商选项,Netdisco QueryRequired 则是服务端 API 行为,两者均独立于本库旧版本接口。
368
371
 
@@ -449,3 +452,13 @@ end
449
452
  `Settings#inventory` 为原始清单适配器包裹 `Inventory::Cache`,默认三天有效。缓存仅保存完整白名单 rows,Rules 和重复地址判定每次重做。身份、SQL 及绑定参数只参与摘要分区,不写入文件;凭据缺失仍在缓存读取前拒绝。过期刷新在有界、按身份的跨进程文件锁内完成,成功后原子替换;失败使用原时间的已验证历史,成功空清单替换旧值。最新查询成功与本地缓存持久化成功是两个事实,不能因写盘失败回退旧数据或重复查询。强刷跳过有效期,不跳过身份、限额、文件校验和失败回退边界。
450
453
 
451
454
  后台心跳和 HTTP 超时观察使用内部 `Concurrency::Observer` 作用域,前台保留主业务值和原始故障;观察线程的异常同步交付所有者,不受宿主全局线程异常策略影响。创建至登记期间延后中断,停止和等待期间保证线程回收;心跳普通故障转换为显示诊断,正常 HTTP 请求的未知观察故障仍传播。
455
+
456
+ ## 领域值与更新边界
457
+
458
+ `Topology::Neighbor`、`Topology::Change`、`Topology::Plan` 各有同名文件,构造时复制并冻结自己拥有的字符串与容器。不可变子对象可在单设备、批次和结果之间共享;调用方的原始容器与格式化回调返回的字符串仍归调用方所有。计划构造校验数据形状,执行边界继续负责地址规范化、命令预检、现场证据重验及读回。
459
+
460
+ `Netdisco::Batch` 复制结果数组和回调诊断,在构造时累计状态计数;`counts` 返回可修改的独立 Hash。未知状态不能满足成功策略。更新结果集合须使用 `with`,它重新计算计数。上述领域值和远程备份目标、回执的 `with` 显式经过构造函数,避免依赖不同 Ruby 版本中原生 `Data#with` 的初始化行为。
461
+
462
+ 拓扑的 `InterfaceBlocks` 模块集中核对 interface 块与解析记录的完整性,由立即生效的变更策略和 Huawei 的只读策略共同组合,读取能力不必继承写入流程。`description_key(row)` 把描述记录映射为领域身份;Junos 逻辑单元使用 `<物理接口>.<单元号>`,保留物理接口描述。FortiOS 读取当前视图顶层 `config system interface`,接口 `description` 与显示 `alias` 分开处理,嵌套 IPv6 表不产生接口记录;无法确认作用域、闭合或记录数量时拒绝结果。
463
+
464
+ `Topology::QuotedText` 是描述字段的纯解码边界,不接触设备会话;厂商策略选择无引号文本的处理规则,解析异常统一携带 host、parse 阶段和 unrecognized_output 代码,不携带字段原文或底层异常 cause。配置模板保留完整带引号字段供业务层解码,不能先删外层引号而留下未还原的转义字符。
data/docs/netdisco.md CHANGED
@@ -76,7 +76,7 @@ The installed command is `net-backup`; source checkouts use `bundle exec bin/net
76
76
 
77
77
  Here `vendor` is the connector platform key, such as `cisco_ios` or `cisco_nxos`, rather than the manufacturer. Filtering and sampling use that key: a limit of five can select five IOS devices and five NX-OS devices. `source_vendor` retains the original inventory vendor label.
78
78
 
79
- Interactive batch examples use `CLI::Backup`; `CLI::Credentials.resolve` supplies the inventory source and per-device credential resolver, while `CLI::Options` parses arguments and configuration overrides. These components live under `netdisco/cli/`.
79
+ Interactive batch examples use `CLI::Backup` and `CLI::Topology`; `CLI::Credentials.resolve` supplies the inventory source and per-device credential resolver, while `CLI::Options` parses arguments and configuration overrides. These components live under `netdisco/cli/`. The examples explicitly pass `load_env: true` to make public environment values defaults below YAML and CLI options. The formal CLI retains its existing environment precedence.
80
80
 
81
81
  `--export IP` reads an existing canonical `<IP>.txt` file offline. It writes original bytes to stdout, or atomically writes a private file with `--output`. It does not require inventory or device credentials. Hostname-prefixed batch-example files are not searched by this command.
82
82
 
@@ -146,7 +146,7 @@ For other independent tasks, load `net/connector/concurrency/pool` and use `Net:
146
146
 
147
147
  ## PostgreSQL inventory
148
148
 
149
- Set `netdisco.source: postgres` and provide your own SELECT query. Table names and business filters are not built into the library. See [inventory_sql.yml](../examples/inventory_sql.yml):
149
+ Set `netdisco.source: postgres` and provide your own SELECT query. Table names and business filters are not built into the library. See [inventory.yml](../examples/inventory.yml):
150
150
 
151
151
  ```yaml
152
152
  netdisco:
@@ -170,7 +170,7 @@ export ND_DB_HOST=database.example
170
170
  export ND_DB_NAME=netdisco
171
171
  export ND_DB_USER=inventory-reader
172
172
  export ND_DB_PASS='replace-me'
173
- net-backup --config examples/inventory_sql.yml --plan
173
+ net-backup --config examples/inventory.yml --plan
174
174
  ```
175
175
 
176
176
  Host, database, user, and password are required. Source, query, and parameters may be overridden by `--source`, `--query`, and `--query-params`. They are public configuration and may appear in `--show-config`; passwords belong only in connection variables. HTTP credentials and device credentials are not prerequisites for a database-only plan.
@@ -89,7 +89,7 @@ net-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
89
89
 
90
90
  ### PostgreSQL 联机查询
91
91
 
92
- 设置 `netdisco.source: postgres` 可以直接从数据库查询清单,继续使用同一套 Fleet、规则、计划和备份流程。库不内置表名、SQL 或业务筛选条件;必须提供查询,可直接修改 [YAML 示例](../examples/inventory_sql.yml):
92
+ 设置 `netdisco.source: postgres` 可以直接从数据库查询清单,继续使用同一套 Fleet、规则、计划和备份流程。库不内置表名、SQL 或业务筛选条件;必须提供查询,可直接修改 [YAML 示例](../examples/inventory.yml):
93
93
 
94
94
  ```yaml
95
95
  netdisco:
@@ -116,10 +116,10 @@ export ND_DB_NAME=netdisco
116
116
  export ND_DB_USER=inventory-reader
117
117
  export ND_DB_PASS='replace-me'
118
118
 
119
- net-backup --config examples/inventory_sql.yml --show-config
120
- net-backup --config examples/inventory_sql.yml --plan
119
+ net-backup --config examples/inventory.yml --show-config
120
+ net-backup --config examples/inventory.yml --plan
121
121
  # 覆盖查询参数;实际备份仍需设置 NC_DEVICE_* 凭据。
122
- net-backup --config examples/inventory_sql.yml --plan --query-params '["Cisco"]'
122
+ net-backup --config examples/inventory.yml --plan --query-params '["Cisco"]'
123
123
  ```
124
124
 
125
125
  SQL、参数和来源也可分别通过 `--query SQL`、`--query-params JSON`、`--source postgres` 覆盖 YAML,不再从环境变量读取。SQL 和参数属于可公开配置,会出现在 `--show-config` 中;数据库密码只放在连接环境变量中。身份凭据不进入策略快照、计划、报告或 `inspect`;端口、TLS 和超时属于公开策略,进入快照与配置摘要。已有 Fleet 每次重新查询时读取最新连接凭据;传入已有 `plan:` 执行时不会重新查询。
@@ -140,7 +140,7 @@ CLI 的计划与批次摘要使用 JSON。默认 `--success-policy strict` 使
140
140
 
141
141
  本地批量示例和 TFTP 批量示例默认全量;显式 `--sample N` 或 YAML `backup.limit_per_vendor` 才限制每厂商数量(示例允许 1 至 5)。优先级为 CLI > YAML。并发默认 4,可用 `--concurrency N`、环境变量 `NC_CONCURRENCY` 或 YAML `backup.concurrency` 覆盖,优先级依次降低。两类示例在备份目录下创建唯一批次目录。TFTP 批量示例在可访问服务器目录时使用 hostname-ip 远端文件名并逐批归档;无法访问服务器目录时使用带批次标识的远端文件名,避免覆盖旧文件;上传完成与服务器文件验证是不同状态。
142
142
 
143
- 全量 TFTP 计划保存为 `plan.json`。目标文件名通常为 `<设备名>-<IP>.cfg`,Radware 用 `.tgz`,山石用 `.dat`。可访问本机 TFTP 服务器目录时,批量示例直接使用 `<设备名>-<IP>.<扩展名>` 上传,并在服务器的 `archive/<批次>/` 与本地 `<批次目录>/tftp/` 各保留一份;上传前已有的同名文件先保存到新归档目录的 `previous/`。无法访问服务器目录时,远端文件名增加东八区批次标识及同秒序号,避免覆盖旧文件;实际文件名见 `summary.json` 的 `remote_path`。计划仍检查同批目标名冲突。PAN-OS 固定使用 `running-config.xml`,还要求 `Sent ... bytes` 完成行;为避免下次上传覆盖它,运行批量示例时必须能访问本机 TFTP 服务器目录,否则该设备会明确失败且不会发起上传。H3C 从 `display startup` 发现源文件,可用 YAML `tftp.h3c_source_file` 覆盖;华为默认 `flash:/startup.cfg`。Nexus 9000 默认 VRF 为 `management`,山石为 `mgt-vr`;YAML `tftp.vrfs` 接受按厂商键配置的映射。山石命令在 `vrouter` 参数后追加唯一的 `.dat` 文件名;直接调用山石连接器且不指定 `path:` 时,由设备生成文件名并在结果中返回。通过 `--tftp-root DIR` 或 YAML `tftp.verification_root` 指定本机或已挂载的服务器目录时,批量示例在每台设备完成后核验实际回执路径、非空文件、修改时间和 SHA-256,并立即保存两份历史文件;报告的 `local_file` 和 `server_archive_file` 分别指向本地批次副本和服务器归档副本。核验或归档失败不会报告成功。未提供本机目录时,可命名设备仍使用不覆盖旧文件的远端文件名,回执保留为 `device_reported`,本地批次不包含配置副本。默认示例使用 `selected` 策略;要求服务器文件核验时使用 `--success-policy verified`,并提供本机服务器目录。可运行 `ruby examples/review_tftp.rb <批次目录>`,根据会话日志复核剩余失败,而不改写原始结果。日志、`events.jsonl` 和逐台结果均以私有权限保存。
143
+ 全量 TFTP 计划保存为 `plan.json`。目标文件名通常为 `<设备名>-<IP>.cfg`,Radware 用 `.tgz`,山石用 `.dat`。可访问本机 TFTP 服务器目录时,批量示例直接使用 `<设备名>-<IP>.<扩展名>` 上传,并在服务器的 `archive/<批次>/` 与本地 `<批次目录>/tftp/` 各保留一份;上传前已有的同名文件先保存到新归档目录的 `previous/`。无法访问服务器目录时,远端文件名增加东八区批次标识及同秒序号,避免覆盖旧文件;实际文件名见 `summary.json` 的 `remote_path`。计划仍检查同批目标名冲突。PAN-OS 固定使用 `running-config.xml`,还要求 `Sent ... bytes` 完成行;为避免下次上传覆盖它,运行批量示例时必须能访问本机 TFTP 服务器目录,否则该设备会明确失败且不会发起上传。H3C 从 `display startup` 发现源文件,可用 YAML `tftp.h3c_source_file` 覆盖;华为默认 `flash:/startup.cfg`。Nexus 9000 默认 VRF 为 `management`,山石为 `mgt-vr`;YAML `tftp.vrfs` 接受按厂商键配置的映射。山石命令在 `vrouter` 参数后追加唯一的 `.dat` 文件名;直接调用山石连接器且不指定 `path:` 时,由设备生成文件名并在结果中返回。通过 `--tftp-root DIR` 或 YAML `tftp.verification_root` 指定本机或已挂载的服务器目录时,批量示例在每台设备完成后核验实际回执路径、非空文件、修改时间和 SHA-256,并立即保存两份历史文件;报告的 `local_file` 和 `server_archive_file` 分别指向本地批次副本和服务器归档副本。核验或归档失败不会报告成功。未提供本机目录时,可命名设备仍使用不覆盖旧文件的远端文件名,回执保留为 `device_reported`,本地批次不包含配置副本。默认示例使用 `selected` 策略;要求服务器文件核验时使用 `--success-policy verified`,并提供本机服务器目录。日志、`events.jsonl` 和逐台结果均以私有权限保存。
144
144
 
145
145
  批量 TFTP 源文件通过 YAML `tftp.h3c_source_file`、`tftp.h3c_wireless_source_file`、`tftp.huawei_source_file` 设置。源文件是设备上的路径,需符合连接器校验规则。
146
146
 
@@ -243,9 +243,9 @@ directory = Net::Connector::Storage::BatchDirectory.create("./backups")
243
243
 
244
244
  批量示例由 `Netdisco::CLI::Backup` 统一编排,`CLI::Options.settings` 为示例和正式 CLI 共用配置覆盖入口。
245
245
 
246
- 终端入口按职责放在 `cli/`:`CLI::Backup` 编排批量备份,`CLI::Credentials.resolve` 解析凭据来源,`CLI::Options` 解析参数和配置覆盖。
246
+ 终端入口按职责放在 `cli/`:`CLI::Backup` 编排批量备份,`CLI::Topology` 编排发现、规划与显式应用,`CLI::Credentials.resolve` 解析凭据来源,`CLI::Options` 解析参数和配置覆盖。示例显式传入 `load_env: true`,将公开环境变量作为 YAML 和 CLI 之下的默认值;正式 CLI 保留既有的环境覆盖规则。
247
247
  示例默认 `selected`,正式 CLI 保留 `strict` 默认;两者均支持显式 `--success-policy`,退出码与报告的 `policy_success` 一致。
248
- 新示例 `summary.json` 使用标准 schema 2 的 `devices`,不再另造 `outcomes`;旧报告可继续用 `review_tftp.rb` 读取。
248
+ 示例 `summary.json` 使用标准 schema 2 的 `devices`,逐台结果与诊断统一保存在批次报告中。
249
249
  TFTP 的 `verification` 汇总与设备条目的 `server_file_verified` 区分上传回执和服务器文件核验,核验成功还包含 `local_file`、`server_archive_file`、`bytes`、`sha256`。
250
250
  报告通过私有原子写入保存;报告保存失败会阻止成功退出,终端最终结果在报告保存之后输出。
251
251
 
data/docs/templates.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  `lib/net/connector/templates/` 统一采用 `<平台>/<完整命令名>[_格式变体].textfsm`。命令使用小写,空格、连字符和路径分隔符转为下划线;不使用缩写命令、字段用途名或额外的命令子目录。管道的输出格式选项保留为命令词,纯分页控制 `no-more` 不进入文件名。
8
8
 
9
- 例如,`show running-config` 对应 `show_running_config.textfsm`,`display current-configuration` 对应 `display_current_configuration.textfsm`,`/cfg/dump` 对应 `cfg_dump.textfsm`,`show configuration | display set` 对应 `show_configuration_display_set.textfsm`。PAN-OS 配置视图的命令是 `show`,其 set 输出对应 `palo_alto/show.textfsm`。H3C 同一命令的列序变体使用 `_name_first` 和 `_local_first` 后缀。
9
+ 例如,`show running-config` 对应 `show_running_config.textfsm`,`display current-configuration` 对应 `display_current_configuration.textfsm`,`/cfg/dump` 对应 `cfg_dump.textfsm`,`show configuration | display set` 对应 `show_configuration_display_set.textfsm`。PAN-OS 配置视图的命令是 `show`,其 set 输出对应 `palo_alto/show.textfsm`。H3C 同一命令的列序由同一模板按表头切换状态。
10
10
 
11
11
  | 路径 | 输入与用途 |
12
12
  | --- | --- |
@@ -17,22 +17,24 @@
17
17
  | `cisco_nxos/show_running_config.textfsm` | NX-OS 运行配置中的接口和描述 |
18
18
  | `cisco_nxos/show_ip_interface_brief.textfsm` | NX-OS 多 VRF 接口、IP 及 protocol/link/admin 状态 |
19
19
  | `h3c/display_current_configuration.textfsm` | Comware 运行配置中的接口和描述 |
20
- | `h3c/display_lldp_neighbor_information_list_name_first.textfsm` | 邻居名称位于首列的 LLDP 列表 |
21
- | `h3c/display_lldp_neighbor_information_list_local_first.textfsm` | 本地接口位于首列的 LLDP 列表 |
20
+ | `h3c/display_lldp_neighbor_information_list.textfsm` | 按表头识别名称首列或本地接口首列的 LLDP 列表 |
21
+ | `huawei/display_lldp_neighbor_brief.textfsm` | VRP LLDP 邻居简表,保留端口类型与编号之间的空格 |
22
+ | `huawei/display_current_configuration.textfsm` | VRP 运行配置中的接口和描述 |
22
23
  | `hillstone/show_configuration_running.textfsm` | StoneOS 运行配置中的接口和描述 |
23
24
  | `hillstone/show_lldp_neighbor_information.textfsm` | StoneOS 的 LLDP 邻居表 |
24
25
  | `palo_alto/show.textfsm` | PAN-OS set 格式中的接口和备注 |
25
26
  | `palo_alto/show_lldp_neighbors_all.textfsm` | PAN-OS 按接口分块的 LLDP 邻居输出 |
26
27
  | `radware/cfg_dump.textfsm` | Alteon 配置转储中的端口名称 |
28
+ | `fortinet/show_full_configuration.textfsm` | 当前视图顶层接口表的描述,跳过嵌套表 |
27
29
  | `fortinet/diagnose_lldp_rx_neighbor.textfsm` | FortiOS 改名前后 LLDP 邻居查询的共同输出 |
28
30
  | `juniper/show_lldp_neighbors_detail.textfsm` | Junos LLDP 详细邻居块 |
29
- | `juniper/show_configuration_display_set.textfsm` | Junos 已提交 set 配置中的物理接口描述 |
31
+ | `juniper/show_configuration_display_set.textfsm` | Junos 已提交 set 配置中的物理接口与逻辑单元描述 |
30
32
 
31
- H3C Wireless 沿用 H3C 模板。H3C 邻居策略检查表头后选择列顺序变体;未知表头直接拒绝,不尝试多个模板后采用碰巧解析出的部分结果。
33
+ H3C Wireless 沿用 H3C 模板。H3C 邻居策略与通用命令解析共用按表头切换列序的模板;未知表头直接拒绝,不尝试多个模板后采用碰巧解析出的部分结果。
32
34
 
33
35
  ## 索引与显式选择
34
36
 
35
- 根目录的 `index` 保持 TextFSM 的 `Template, Vendor, Command` 格式。Template 使用相对资源根目录的路径,Vendor 是连接器平台标识,Command 是命令匹配规则。一个共享模板可以被多个平台索引项引用。
37
+ 根目录的 `index` 保持 TextFSM 的 `Template, Vendor, Command` 格式。Template 使用相对资源根目录的路径,Vendor 是连接器平台标识,Command 是命令匹配规则。内置索引的 Vendor 与 Command 均使用 `\A...\z` 完整匹配,防止前缀厂商名称及带额外命令片段的输入误选模板。一个共享模板可以被多个平台索引项引用。
36
38
 
37
39
  ```ruby
38
40
  parser = Net::Connector::TextFSM.new
@@ -40,7 +42,7 @@ rows = parser.call(output, vendor: :cisco_ios, command: "show ip interface brief
40
42
  interfaces = parser.call(config, template: "cisco/show_running_config.textfsm")
41
43
  ```
42
44
 
43
- 命令索引处理固定输出格式。依赖输出表头或视图上下文的模板由对应厂商策略显式选择。调用方也可以提供独立的 `template_dir:`,其索引和路径遵循相同规则。
45
+ 命令索引覆盖内置配置及邻居模板;H3C 列序在模板状态机内选择。模板本身只解析记录,厂商策略仍负责表头、完整性及作用域校验。调用方也可以提供独立的 `template_dir:`,其索引和路径遵循相同规则。
44
46
 
45
47
  ## 修改与验证
46
48
 
@@ -51,3 +53,7 @@ interfaces = parser.call(config, template: "cisco/show_running_config.textfsm")
51
53
  NX-OS 模板独立放在 `cisco_nxos/`,CDP 按邻居块清理状态并与设备块数量核对,残缺块不能借用下一条记录的字段。接口描述在空行或下一个顶层配置段结束,防止 route-map 等后续段落的描述覆盖接口描述。IP 接口摘要保留 `VRF`、`INTERFACE`、`IP_ADDRESS`、`PROTOCOL`、`LINK_STATUS`、`ADMIN_STATUS` 六个字段;`show ip interface brief` 与带 `vrf` 参数的命令共用此模板。
52
54
 
53
55
  输出字段参考 [Cisco NX-OS CDP 命令](https://developer.cisco.com/docs/nx-api-cli-reference-for-the-cisco-nexus-7000-series-platform/cdp-commands/)与 [Cisco Nexus IP 接口摘要](https://developer.cisco.com/docs/cisco-nexus-3000-and-9000-series-nx-api-rest-sdk-user-guide-and-api-reference-release-103x/querying-a-summary-of-ip-interface-status-and-configuration-information/)。测试使用脱敏合成输出,不代表真实设备及所有固件均已验证。
56
+
57
+ ## 描述字段的词法与业务值
58
+
59
+ 带引号的描述字段在模板中保留完整词法文本,`Topology::QuotedText` 负责还原引号与转义,并将不完整字段转为不含原文的领域错误。Junos、FortiOS、PAN-OS 与 Radware 使用同一解码边界;是否拆分无引号文本由厂商策略明确指定。接口不存在描述语句时可返回空描述,`description ""` 等显式空字符串也有明确含义;缺少值的 description/comment/name 语句则是残缺输入,不能被默认空字段掩盖。
data/docs/usage.md CHANGED
@@ -61,7 +61,7 @@ H3C discovers its saved source with `display startup` when omitted; Huawei requi
61
61
 
62
62
  The immutable `TftpReceipt` records `server`, actual `path`, `completed_at`, `configuration_kind`, `source_file`, `format`, `requested_path`, and verification fields. Direct calls return `:device_reported` with no server digest: they confirm device output, not the server file. Explicit source files are not assumed to contain running configuration. TFTP carries configuration without encryption; restrict its network access appropriately.
63
63
 
64
- Explicit failure raises `:transfer_failed`; missing completion evidence raises `:transfer_unconfirmed`. A failure after confirmed upload raises `TftpCompletionError` with `error.receipt`. A mismatched explicit path is `:transfer_path_mismatch`; an unconfirmed actual path is `:transfer_path_unconfirmed` with receipt path `nil`. No automatic retransmission occurs. See the [single-device example](../examples/device_tftp.rb) and [server verification](netdisco.md#tftp-history-and-verification).
64
+ Explicit failure raises `:transfer_failed`; missing completion evidence raises `:transfer_unconfirmed`. A failure after confirmed upload raises `TftpCompletionError` with `error.receipt`. A mismatched explicit path is `:transfer_path_mismatch`; an unconfirmed actual path is `:transfer_path_unconfirmed` with receipt path `nil`. No automatic retransmission occurs. For a single inventory device, run `bundle exec ruby examples/backup_tftp.rb --host 192.0.2.20` with the server and transfer options in [backup.yml](../examples/backup.yml). See [server verification](netdisco.md#tftp-history-and-verification).
65
65
 
66
66
  ## TextFSM parsing
67
67
 
@@ -145,6 +145,6 @@ Per-command prompts use `interactions: [Net::Connector::Interaction.new(/Token:\
145
145
 
146
146
  ## Dependencies and extensions
147
147
 
148
- Load device APIs with `require "net/connector"`; Netdisco has a separate entrypoint. Vendor implementations and TextFSM load on demand. Runtime dependencies declare minimum versions; the host application's lockfile selects compatible versions. Published TextFSM 0.2.0 constrains JSON to 2.x. The explicit JSON 3 source-patch matrix does not establish compatibility of the published dependency.
148
+ Load device APIs with `require "net/connector"`; Netdisco has a separate entrypoint. Vendor implementations and TextFSM load on demand. Runtime dependencies have no version constraints; the host application's lockfile selects compatible versions. Published TextFSM 0.2.0 constrains JSON to 2.x. The explicit JSON 3 source-patch matrix does not establish compatibility of the published dependency.
149
149
 
150
150
  Examples use the development dependency `dotenv` to read the project `.env`; the library and CLI do not. Follow the [loading and migration reference](architecture.md#加载入口与厂商策略) (Chinese) for custom profiles, strategies, and protected hooks.
data/docs/usage.zh-CN.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  脱敏直接复用 expect-pty 从 0.5.0 起公开的 `Expect::Redactor` 接口;连接器只管理秘密作用域和配置输出的隐私策略。
8
8
 
9
- 运行时依赖只声明所需最低版本,不设置缺少兼容性依据的上限;宿主应用通过自己的 Gemfile/锁文件选择版本。正式发布依赖的安装验证使用 JSON 2。RubyGems 上 textfsm 0.2.0 仍约束 json ~> 2.0;JSON 3 使用固定 TextFSM 源码和显式依赖声明补丁单独验证,不代表公开依赖已经支持 JSON 3。开发工具及兼容测试矩阵的版本约束不影响 gem 使用者。
9
+ 运行时依赖不设置版本约束,宿主应用通过自己的 Gemfile 和锁文件选择兼容版本。正式发布依赖的安装验证使用 JSON 2。RubyGems 上 textfsm 0.2.0 仍约束 json ~> 2.0;JSON 3 使用固定 TextFSM 源码和显式依赖声明补丁单独验证,不代表公开依赖已经支持 JSON 3。开发工具及兼容测试矩阵的版本约束不影响 gem 使用者。
10
10
 
11
11
  示例脚本通过开发依赖 `dotenv` 自动读取项目根目录 `.env`(先执行 `bundle install`),已有进程环境变量优先;库和 CLI 本身不会隐式读取 `.env`。
12
12
 
@@ -72,7 +72,7 @@ end
72
72
 
73
73
  策略先校验参数组合,再在同一次会话租约中完成源探测、上传、证据检查和收尾。上传已确认但日志或清理失败时抛出 `TftpCompletionError`,通过 `error.receipt` 保留完成事实。显式请求与实际路径不同返回 `:transfer_path_mismatch`;实际路径无法安全确认返回 `:transfer_path_unconfirmed`,此时回执的 `path` 为 `nil`。这些错误不会自动重传;Fleet 保留上传事实并标为 `reported_with_error`。
74
74
 
75
- 可运行[单设备示例](../examples/device_tftp.rb):`ruby examples/device_tftp.rb --vendor huawei --host 192.0.2.20 --server 192.0.2.30 --source-file flash:/startup.cfg --path site/router.cfg`。凭据使用 `NC_DEVICE_USERNAME`、`NC_DEVICE_PASSWORD`;`--source-file`、`--path`、`--vrf` 指定传输选项。
75
+ 清单内的单台设备使用统一[批量入口](../examples/backup_tftp.rb):`bundle exec ruby examples/backup_tftp.rb --host 192.0.2.20`。服务器、源文件和 VRF 在 [backup.yml](../examples/backup.yml) 中配置;凭据使用 `NC_DEVICE_USERNAME`、`NC_DEVICE_PASSWORD`。需要指定自定义远端路径时,使用上面的 `device.tftp_backup` API。
76
76
 
77
77
  只需在内存中采集配置时,调用 `device.running_config`;它返回 `Result`,`result.value!` 返回清理后的文本,失败时抛出对应错误。
78
78
 
data/docs/vendors.md CHANGED
@@ -5,8 +5,8 @@
5
5
  | 平台键 | 系统与登录视图 | 采集命令 | 保存 | 原生远端备份 | 拓扑读取 |
6
6
  | --- | --- | --- | --- | --- | --- |
7
7
  | `topsec` | TopOS 防火墙,`TopsecOS#` 或 `#` 管理视图 | `show-running nostop` | 未开放 | 使用本地采集备份 | 未开放 |
8
- | `fortinet` | FortiGate/FortiOS,当前账号授权的根或 VDOM 视图 | `show full-configuration` | `execute cfg save` | TFTP `execute backup full-config tftp` | LLDP 邻居 |
9
- | `juniper` | Junos OS,`user@host>` 操作视图 | `show configuration \| display set \| no-more` | 已提交配置已持久化;不隐式提交候选库 | FTP/SCP `show configuration \| save` | LLDP 邻居、物理接口描述 |
8
+ | `fortinet` | FortiGate/FortiOS,当前账号授权的根或 VDOM 视图 | `show full-configuration` | `execute cfg save` | TFTP `execute backup full-config tftp` | LLDP 邻居、当前视图接口描述 |
9
+ | `juniper` | Junos OS,`user@host>` 操作视图 | `show configuration \| display set \| no-more` | 已提交配置已持久化;不隐式提交候选库 | FTP/SCP `show configuration \| save` | LLDP 邻居、物理接口及逻辑单元描述 |
10
10
 
11
11
  这些能力通过合成会话、解析样例和安装检查验证,尚无真实设备验收记录。Topsec 只实现有检索依据的 TopOS 配置读取方言;其他系列需依据其 CLI 手册接入,不能套用 Cisco 的命令。
12
12
 
@@ -36,7 +36,7 @@ TFTP 导出包括默认值,不接受 `source_file:` 或 `vrf:`。成功必须
36
36
 
37
37
  LLDP 查询先读取 `get system status`,明确解析版本后选择命令:7.6.2 及以前使用 `diagnose lldprx neighbor`,7.6.3 起使用 `diagnose lldp rx neighbor`。无法识别版本时直接报解析错误,不猜测命令。接口行数量与解析记录必须一致,空响应不会被当作零邻居。
38
38
 
39
- Junos LLDP 使用 `show lldp neighbors detail | no-more`,逐个本地接口块核对记录。残缺记录及只有标题的输出会报错;接口描述从已提交的 set 配置中读取,保留 `ge-`、`xe-`、`ae` 等原接口身份。
39
+ Junos LLDP 使用 `show lldp neighbors detail | no-more`,逐个本地接口块核对记录。残缺记录及只有标题的输出会报错;接口描述从已提交的 set 配置中读取,逻辑单元用 `接口.单元号` 独立返回,保留 `ge-`、`xe-`、`ae` 等原接口身份。
40
40
 
41
41
  ## 命令依据
42
42
 
@@ -48,3 +48,14 @@ Junos LLDP 使用 `show lldp neighbors detail | no-more`,逐个本地接口块
48
48
  - [Juniper save 配置模式语义](https://www.juniper.net/documentation/us/en/software/junos/cli-reference/topics/ref/statement/save-cli-config.html):配置模式的层级导出与远端 SCP。
49
49
  - [Juniper file copy 协议](https://www.juniper.net/documentation/us/en/software/junos/cli-reference/topics/ref/command/file-copy.html):支持的远端文件传输 URL。
50
50
  - [Juniper LLDP 邻居字段](https://www.juniper.net/documentation/us/en/software/junos/cli-reference/topics/ref/command/show-lldp-neighbors.html):接口、机箱、端口和系统名字段,`detail` 选项要求 Junos 19.1R2 或以上。
51
+
52
+ ## 拓扑读取能力对齐
53
+
54
+ Huawei VRP 通过 `display lldp neighbor brief` 提供 LLDP 邻居,运行配置提供接口描述。简表不包含机箱 ID,邻居的 `chassis_id` 保留为空;接口类型与编号之间的空格在返回值中保留,在配置身份关联时移除。没有已验证的提交与保存阶段,因此只开放读取。
55
+
56
+ FortiOS 从既有 `show full-configuration` 结果中读取顶层 `config system interface` 的 `description`,不把 `alias` 当作描述,不切换 VDOM 或全局视图。嵌套 VDOM 接口表、多个顶层接口表、缺少 next/end 或未闭合描述会报解析错误。输入按标准 CLI 的四空格缩进组织;不对其他导出格式作静默猜测。
57
+
58
+ - [Huawei LLDP 邻居简表](https://info.support.huawei.com/hedex/api/pages/EDOC1100277650/AZM1016J/04/resources/command/yunshan/LLDPDISPLAYNEIBRIEF%28LLDPOM%29.html):查询视图、字段及端口拼写。
59
+ - [Huawei 当前接口配置](https://info.support.huawei.com/hedex/api/pages/EDOC1100363264/AEN0403J/05/resources/command/yunshan/BUILDRUNINTERFACE1%28CLIOM%29.html):接口块及生效配置的范围。
60
+ - [FortiOS 接口配置结构](https://docs.fortinet.com/document/fortigate/7.4.4/cli-reference/317104469/config-system-interface):description、alias 与 IPv6 等嵌套配置。
61
+ - [Junos 物理接口与逻辑单元描述示例](https://www.juniper.net/documentation/en_US/day-one-books/DayOne-Intro-SRv6.pdf):物理描述与 unit 描述的独立配置层级。
data/examples/backup.yml CHANGED
@@ -1,11 +1,15 @@
1
1
  # 仅保存非敏感策略,凭据使用环境变量。
2
- # ND_URL、ND_CACHE_TTL、NC_CONCURRENCY 可覆盖对应策略;其余由 YAML 或 CLI 设置。
2
+ # 示例层优先级:CLI > YAML > 环境默认值;进程环境优先于 .env。
3
3
  backup:
4
4
  directory: ./examples/backups
5
5
  concurrency: 4
6
6
  # limit_per_vendor: 3 # 省略时使用全量清单。
7
7
  netdisco:
8
8
  source: http
9
+ cache_enabled: true
10
+ cache_mode: prefer # prefer / only / refresh;CLI 可覆盖。
11
+ cache_ttl: 259200
12
+ cache_directory: ./.cache/net-connector/inventory
9
13
  page_size: 500
10
14
  max_pages: 10000
11
15
  max_response_bytes: 16777216
@@ -16,6 +20,10 @@ netdisco:
16
20
  inventory:
17
21
  include_hosts: []
18
22
  exclude_hosts: []
23
+ include_names: []
24
+ exclude_names: []
25
+ name_match: contains # contains / prefix / glob;不区分大小写,排除优先。
26
+ exclude_vendors: []
19
27
  include_vendors: []
20
28
  vendor_overrides: {}
21
29
  host_overrides: {}
@@ -32,10 +40,41 @@ ssh:
32
40
  max_script_output_bytes: null
33
41
  tftp:
34
42
  vrfs:
35
- cisco_nxos: management
36
- hillstone: mgt-vr
37
- # verification_root: /srv/tftp
38
- # server: 192.0.2.10
39
- # h3c_source_file: flash:/startup.cfg
40
- # h3c_wireless_source_file: flash:/startup.cfg
41
- # huawei_source_file: flash:/startup.cfg
43
+ cisco_nxos:
44
+ default: management
45
+ hosts:
46
+ "192.0.2.21": production # 设备值覆盖 default;null 回到设备协议缺省。
47
+ hillstone:
48
+ default: mgt-vr
49
+ hosts:
50
+ "192.0.2.22": trust-vr
51
+ source_interfaces:
52
+ h3c:
53
+ default: management # 单次 source ip,使用本设备连接 IPv4 地址。
54
+ hosts:
55
+ "192.0.2.23": Vlan-interface10
56
+ "192.0.2.24": null # 清除 management,让设备使用协议缺省源。
57
+ radware:
58
+ default: management # 单次 -mgmt;data 对应 -data。
59
+ hosts:
60
+ "192.0.2.25": data
61
+ # 以下地址均为文档保留地址,运行前替换为设备可达的实际服务器。
62
+ server: 192.0.2.10
63
+ servers:
64
+ "192.0.2.26": "192.0.2.20" # 仅此设备改用另一个服务器。
65
+ verification_roots: {} # 精确服务器到本机目录;设备切换服务器后不会沿用默认目录。
66
+ # 启用核验时将上面的 {} 替换为实际可写目录映射,例如:
67
+ # verification_roots:
68
+ # "192.0.2.10": /srv/tftp/main
69
+ # "192.0.2.20": /srv/tftp/alternate
70
+ source_files:
71
+ h3c:
72
+ default: null # 未指定时从设备发现;也可配置 flash:/startup.cfg。
73
+ hosts:
74
+ "192.0.2.23": flash:/startup.cfg
75
+ h3c_wireless:
76
+ default: null
77
+ hosts: {}
78
+ huawei:
79
+ default: flash:/startup.cfg
80
+ hosts: {}
@@ -0,0 +1,39 @@
1
+ # PostgreSQL 完整批量示例;凭据使用 ND_DB_HOST/NAME/USER/PASS。
2
+ # 只读参数化查询,不把数据库密码写入 YAML。
3
+ netdisco:
4
+ source: postgres
5
+ db_port: 5432
6
+ db_sslmode: verify-full
7
+ # db_sslrootcert: /etc/net-connector/database-ca.crt
8
+ # db_connect_timeout: 10
9
+ query: |
10
+ SELECT host(ip) AS ip, name, dns, vendor, os, model, os_ver, serial
11
+ FROM device
12
+ WHERE vendor = $1
13
+ ORDER BY ip
14
+ query_params: [H3C]
15
+ page_size: 500
16
+ max_devices: 100000
17
+ inventory_timeout: 300
18
+ cache_enabled: true
19
+ cache_mode: prefer
20
+ cache_ttl: 259200
21
+ cache_directory: ./.cache/net-connector/inventory
22
+ inventory:
23
+ include_hosts: []
24
+ exclude_hosts: []
25
+ include_names: []
26
+ exclude_names: []
27
+ name_match: contains
28
+ include_vendors: []
29
+ exclude_vendors: []
30
+ vendor_overrides: {}
31
+ host_overrides: {}
32
+ device_rules: []
33
+ backup:
34
+ directory: ./examples/backups
35
+ concurrency: 4
36
+ ssh:
37
+ protocol: ssh
38
+ host_key_policy: accept_new
39
+ log_level: info
@@ -74,7 +74,7 @@ module Net
74
74
  end
75
75
  @topology_strategy = strategy_class(topology_strategy, :topology_strategy,
76
76
  %i[neighbor_command neighbor_template expected_neighbor_count empty_neighbor_output?
77
- protocol description_template interface_key configuration_interface
77
+ protocol description_template description_key interface_key configuration_interface
78
78
  decode_description validate_descriptions! change_error_code supports?
79
79
  enter_configuration_command change_commands script_command
80
80
  leave_configuration_commands verification_commands persistence_commands persistence_confirmed?])
@@ -16,6 +16,10 @@ module Net
16
16
  super(server: server.dup.freeze, path: path.dup.freeze, protocol: protocol, username: username.dup.freeze,
17
17
  completed_at: completed_at.dup.freeze, configuration_kind: configuration_kind, verification: verification)
18
18
  end
19
+
20
+ # Ruby 3.2 的原生更新跳过构造校验;更新也必须建立经过校验的快照。
21
+ def with(**attributes) = attributes.empty? ? self : self.class.new(**to_h.merge(attributes))
22
+
19
23
  end
20
24
 
21
25
  # 完成后出现收尾错误时保留上传事实,避免调用方盲目重传。
@@ -23,6 +23,9 @@ module Net
23
23
  super(host: host.dup.freeze, path: path.dup.freeze, username: username.dup.freeze, protocol: protocol)
24
24
  end
25
25
 
26
+ # Ruby 3.2 的原生更新跳过构造校验;更新也必须建立经过校验的快照。
27
+ def with(**attributes) = attributes.empty? ? self : self.class.new(**to_h.merge(attributes))
28
+
26
29
  def url
27
30
  "#{protocol}://#{username}@#{host}/#{path}"
28
31
  end
@@ -28,9 +28,10 @@ module Net
28
28
  def remote_path(target, _result) = target.path
29
29
 
30
30
  # 单次上传共用预检、命令预算和应答封装;厂商只提供协议命令与交互差异。
31
- def script(target, source_file:, vrf: nil)
31
+ def script(target, source_file:, vrf: nil, source_interface: nil)
32
+ validate_source_interface!(target, source_interface: source_interface)
32
33
  validate_options!(target, source_file: source_file, vrf: vrf)
33
- command = transfer_command(target, source_file: source_file, vrf: vrf)
34
+ command = transfer_command(target, source_file: source_file, vrf: vrf, source_interface: source_interface)
34
35
  interactions = transfer_interactions(target, source_file: source_file, vrf: vrf)
35
36
  Script.new([Command.new(command, timeout: 180, interactions: interactions)])
36
37
  end
@@ -46,6 +47,10 @@ module Net
46
47
  end
47
48
  end
48
49
 
50
+ def validate_source_interface!(_target, source_interface:)
51
+ raise ArgumentError, "source_interface is not supported by this TFTP strategy" unless source_interface.nil?
52
+ end
53
+
49
54
  private
50
55
 
51
56
  def transfer_command(_target, **)