net-connector 0.4.1 → 0.5.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/CONTRIBUTING.md +40 -0
  4. data/README.md +93 -18
  5. data/SECURITY.md +11 -0
  6. data/docs/VERIFICATION.md +139 -6
  7. data/docs/architecture.md +248 -39
  8. data/lib/net/connector/device/base.rb +28 -101
  9. data/lib/net/connector/device/local_backup.rb +96 -0
  10. data/lib/net/connector/device/profile.rb +8 -3
  11. data/lib/net/connector/device/running_config/strategy.rb +1 -1
  12. data/lib/net/connector/device/running_config.rb +46 -5
  13. data/lib/net/connector/device/save_config.rb +21 -0
  14. data/lib/net/connector/device/tftp/file_upload.rb +53 -0
  15. data/lib/net/connector/device/tftp/receipt.rb +71 -0
  16. data/lib/net/connector/device/tftp/strategy.rb +67 -0
  17. data/lib/net/connector/device/tftp/target.rb +63 -0
  18. data/lib/net/connector/device/tftp.rb +117 -0
  19. data/lib/net/connector/device/topology/immediate_strategy.rb +40 -0
  20. data/lib/net/connector/device/topology/interface_description.rb +46 -0
  21. data/lib/net/connector/device/topology/interface_name.rb +58 -0
  22. data/lib/net/connector/device/topology/strategy.rb +75 -0
  23. data/lib/net/connector/device/topology.rb +277 -0
  24. data/lib/net/connector/engine/authentication.rb +1 -1
  25. data/lib/net/connector/engine/command.rb +14 -2
  26. data/lib/net/connector/engine/configuration.rb +11 -8
  27. data/lib/net/connector/engine/dialogue.rb +59 -35
  28. data/lib/net/connector/engine/error_metadata.rb +50 -0
  29. data/lib/net/connector/engine/errors.rb +55 -56
  30. data/lib/net/connector/engine/execution.rb +44 -10
  31. data/lib/net/connector/engine/log/event.rb +66 -0
  32. data/lib/net/connector/engine/log/formatter.rb +16 -0
  33. data/lib/net/connector/engine/log/messages.rb +52 -0
  34. data/lib/net/connector/engine/log/stream.rb +56 -0
  35. data/lib/net/connector/engine/log.rb +107 -84
  36. data/lib/net/connector/engine/session.rb +115 -43
  37. data/lib/net/connector/engine/terminal_renderer.rb +7 -4
  38. data/lib/net/connector/engine/terminal_text.rb +33 -0
  39. data/lib/net/connector/engine/transport.rb +2 -2
  40. data/lib/net/connector/netdisco/batch.rb +25 -33
  41. data/lib/net/connector/netdisco/cli.rb +71 -31
  42. data/lib/net/connector/netdisco/client.rb +206 -73
  43. data/lib/net/connector/netdisco/config_file.rb +26 -4
  44. data/lib/net/connector/netdisco/device.rb +4 -5
  45. data/lib/net/connector/netdisco/diagnostic.rb +61 -0
  46. data/lib/net/connector/netdisco/fleet.rb +96 -66
  47. data/lib/net/connector/netdisco/inventory_budget.rb +49 -0
  48. data/lib/net/connector/netdisco/report.rb +115 -0
  49. data/lib/net/connector/netdisco/result_store.rb +2 -2
  50. data/lib/net/connector/netdisco/rules.rb +28 -5
  51. data/lib/net/connector/netdisco/settings.rb +188 -85
  52. data/lib/net/connector/netdisco/worker.rb +26 -17
  53. data/lib/net/connector/storage/backup_lock.rb +116 -0
  54. data/lib/net/connector/storage/private_file.rb +109 -0
  55. data/lib/net/connector/storage/safe_file.rb +62 -0
  56. data/lib/net/connector/{operations → storage}/saved_config.rb +25 -20
  57. data/lib/net/connector/storage.rb +15 -0
  58. data/lib/net/connector/textfsm.rb +72 -0
  59. data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +12 -5
  60. data/lib/net/connector/vendor/cisco_ios/topology.rb +12 -6
  61. data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +11 -4
  62. data/lib/net/connector/vendor/h3c/tftp_backup.rb +9 -4
  63. data/lib/net/connector/vendor/h3c/topology.rb +11 -6
  64. data/lib/net/connector/vendor/h3c.rb +3 -3
  65. data/lib/net/connector/vendor/hillstone/tftp_backup.rb +15 -6
  66. data/lib/net/connector/vendor/hillstone/topology.rb +11 -6
  67. data/lib/net/connector/vendor/huawei/tftp_backup.rb +9 -3
  68. data/lib/net/connector/vendor/palo_alto/running_config.rb +1 -1
  69. data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +17 -8
  70. data/lib/net/connector/vendor/palo_alto/topology.rb +9 -7
  71. data/lib/net/connector/vendor/radware/tftp_backup.rb +12 -5
  72. data/lib/net/connector/vendor/radware/topology.rb +3 -2
  73. data/lib/net/connector/version.rb +1 -1
  74. data/lib/net/connector.rb +1 -6
  75. metadata +54 -38
  76. data/lib/net/connector/device/interface_description.rb +0 -44
  77. data/lib/net/connector/device/interface_name.rb +0 -56
  78. data/lib/net/connector/engine/base.rb +0 -4
  79. data/lib/net/connector/engine/log_messages.rb +0 -47
  80. data/lib/net/connector/engine/profile.rb +0 -4
  81. data/lib/net/connector/engine.rb +0 -4
  82. data/lib/net/connector/operations/local_backup.rb +0 -49
  83. data/lib/net/connector/operations/parse_output.rb +0 -60
  84. data/lib/net/connector/operations/private_file.rb +0 -25
  85. data/lib/net/connector/operations/running_config/cisco.rb +0 -14
  86. data/lib/net/connector/operations/running_config/hillstone.rb +0 -12
  87. data/lib/net/connector/operations/running_config/palo_alto.rb +0 -12
  88. data/lib/net/connector/operations/running_config/strategy.rb +0 -3
  89. data/lib/net/connector/operations/running_config.rb +0 -16
  90. data/lib/net/connector/operations/tftp/cisco_ios.rb +0 -11
  91. data/lib/net/connector/operations/tftp/cisco_nxos.rb +0 -11
  92. data/lib/net/connector/operations/tftp/file_upload.rb +0 -42
  93. data/lib/net/connector/operations/tftp/h3c.rb +0 -11
  94. data/lib/net/connector/operations/tftp/hillstone.rb +0 -11
  95. data/lib/net/connector/operations/tftp/huawei.rb +0 -11
  96. data/lib/net/connector/operations/tftp/palo_alto.rb +0 -11
  97. data/lib/net/connector/operations/tftp/radware.rb +0 -11
  98. data/lib/net/connector/operations/tftp/strategy.rb +0 -75
  99. data/lib/net/connector/operations/tftp_backup.rb +0 -120
  100. data/lib/net/connector/operations/topology/cisco.rb +0 -11
  101. data/lib/net/connector/operations/topology/h3c.rb +0 -11
  102. data/lib/net/connector/operations/topology/hillstone.rb +0 -11
  103. data/lib/net/connector/operations/topology/palo_alto.rb +0 -11
  104. data/lib/net/connector/operations/topology/radware.rb +0 -11
  105. data/lib/net/connector/operations/topology/strategy.rb +0 -67
  106. data/lib/net/connector/operations/topology.rb +0 -193
  107. data/lib/net/connector/operations.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ece440472f988bc86cc30ddb72d954925b57da221ad7ccbf6130728d343abc1
4
- data.tar.gz: e5ea0f6feab21317d18ca21dfe1427c6b24142fcbf7438778ae3c09ea1734399
3
+ metadata.gz: fdd886424e85114a4773396098117ed0e89ddbfe4e4ad3b1dc43dfc003dbc589
4
+ data.tar.gz: f29240ec971ecddb62f9d8d31bd45db027ae0e799c5c14872e8dfdf55ad2f0be
5
5
  SHA512:
6
- metadata.gz: ca6a774c01e474fb5f1140a1e1cdd848ae0a57dd518fab50408fd68c392cd8bea64567ec332229dc210aa98d2bba58bfb7532b6edca1c5bb43e722e604422119
7
- data.tar.gz: f69e9dc32469d1f9ba6b1c8107be385dbe9704dc8b607a4c7631cf11fe98a34b1d0e682d19d436f0937e6f67d97650c934a0f123cf716f03440b0ca204b0c531
6
+ metadata.gz: 97f0d16f83f74c24caf69d3beaa1dbb0c01b3fed6eb58a1609c385eb48eba9acfaa9bb47250d18d91274b06412aada14d1baed6b8e4531603987a4a84505657d
7
+ data.tar.gz: 343cf7298e695ea38fab06a4e838fbb643de8393a4c9ed87ddaee77401eccd5aeeaead086cb9408cb5321a861be423e43b525488a5d06d9014ced050fa3942bd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.5.0 - 2026-09-27
4
+
5
+ 本版包含不兼容的接口调整,不保留旧路径或方法别名;升级时请按[当前接口说明](https://github.com/gatework/net-connector/blob/v0.5.0/docs/architecture.md#当前命名与接口调整)更新调用方。
6
+
7
+ - 统一设备能力的入口与实现:配置采集/保存、本地备份、TFTP 和拓扑归入 `device/`,`Base` 通过能力模块组合;接口名称与描述规则归入 `Topology`,文件锁、私有读写及离线配置归入 `Storage`,厂商差异只保留在 `vendor/`。
8
+ - 移除 `Operations` 命名空间及目录、厂商转发别名,以及 `engine`、`engine/base`、`engine/profile` 旧设备入口,不保留兼容转发。这是开发阶段的接口整理;调用方直接采用当前路径与接口。
9
+ - TextFSM 适配统一为 `Net::Connector::TextFSM`(`textfsm.rb`),实际解析时才加载外部依赖;UTF-8 字节校验及严格终端渲染抽到 `TerminalText`,供解析和拓扑证据检查共用,不转码或修改原始配置。
10
+ - 日志拆分为事件、formatter 和流组件,继续使用标准库 `Logger` 与 expect-pty 脱敏。增加会话/命令标识、操作、阶段、耗时、字节数和错误码;逐行回显带相同上下文,脚本后处理失败另有完成事件,应用 formatter 可读取冻结的安全字段。
11
+ - 实时遵守注入 logger 的级别,保留 formatter/progname/资源所有权;自定义事件保留安全字段,敏感范围隐藏任意载荷。统一公开日志入口为 `log_event`,移除 `record_event`;不再重复发送 `command_detail`,耗时和字节数直接放在 `command_complete`。
12
+ - 失败日志与批次报告共用错误类型、错误码和阶段词表,避免自定义钩子将未登记的配置正文通过错误元数据写入日志;TFTP 回执严格区分未知路径 nil 与无效路径,手工构造及 `with` 更新执行相同校验。
13
+ - 敏感命令、交互和追加查询的保护覆盖最终处理,直接返回失败结果也会脱敏;错误归一化保留业务回执,租约结束时日志故障保留已完成步骤,避免丢失设备已执行的证据。
14
+
15
+ - 统一动作命名:单命令入口为 `execute_command`,脚本为 `execute_script`;日志用 `log_event`,抛错校验用 `validate_*!`,错误构造用 `build_*_error`,作用域用明确的 `with_*`。删除 `run`、`collect_config` 和厂商标识别名,调用方、示例及测试同步更新。
16
+ - 删除 `LegacyIndex` 与旧备份命名查找,只以规范管理地址文件作为本地备份、比较和导出入口;保留路径锁、同 FD 文件校验及原子持久化保护。
17
+ - TFTP 统一通过 `tftp_backup` 返回直接包含来源、格式和实际路径的 `TftpReceipt`,完成后错误只提供 `receipt`。移除双入口、原三字段结果和动态策略兜底,策略接口在 Profile 构造时完整校验。
18
+ - Fleet 始终返回 `Netdisco::Report`,统一 schema 2 和受控诊断,移除 `report_schema` 与 CLI 版本选项。Outcome 的耗时和诊断直接成为 Data 成员;未测量的耗时为 nil,不用墙钟推算。
19
+ - `Storage::PrivateFile.write` 统一返回持久化回执,移除 `write_receipt` 双入口;业务导出和报告存储从回执读取目标路径。
20
+
21
+ ## 0.4.2 - 2026-09-27
22
+
23
+ - 增加带 Ruby/依赖版本和未命中位置的覆盖率 JSON,以 NC-00 实测计数检查同环境覆盖率不下降;CI 分别保存平台报告,并检查最低 expect-pty/textfsm 组合的全量测试及隔离安装。
24
+ - 补充厂商合成证据索引、完整/截断 PAN-OS 引号回归及公开方法返回/抛错契约,区分响应完成、状态读回和持久化确认;协作取消及 TFTP 服务端核验明确列为后置可选项。
25
+
26
+ - 新增离线内存基准,分开记录响应、渲染、清理、解析、结果保留及聚合输出成本,保留机器和原始统计;小型正确性烟测进入本地 CI。
27
+ - 新增可选 `max_script_output_bytes`(默认 nil),主命令和追加查询累计原始响应字节,发送前及完成后检查;超额仍保留已完成步骤,停止后续命令,不自动重试。YAML/ENV/CLI 与不可变批次策略接入,原单响应上限不变。
28
+ - 批量备份共享惰性的旧命名文件索引:规范文件全命中时不扫描,迁移目录每批最多扫描一次,新批次刷新;旧文件身份在读取前后变化时明确失败,保留规范路径优先和歧义拒绝。
29
+ - Netdisco 入口和离线导出延迟加载 TextFSM;解析严格校验原文与终端编辑后的 UTF-8,非法字节返回 `invalid_output_encoding`,不继续生成记录或拓扑计划。日志显示和原始备份字节保持原行为。
30
+ - 保留默认 strict 成功规则和旧 JSON;新增显式 selected 策略及 v2 Report 包装。selected 只允许过滤/采样跳过,部分成功、其他跳过、未知状态和回调/报告故障仍阻止成功;独立显示策略结果与清单覆盖,旧 Batch/Outcome 的 Data 成员不变。
31
+ - v2 诊断只接受受控错误码、类型、阶段和匹配产物回执,不携带异常原消息、输出、命令、source 或 line。设备和 v2 批次耗时改用单调时钟,UTC 审计时间保留;旧自定义 ResultStore 的 write 签名不变。
32
+ - TFTP 内置厂商参数在源文件探测前预检,探测、上传及完成回执共用一次会话租约。旧扩展策略保持调用契约,重写脚本后需显式实现新钩子才能启用相应预检和来源声明。
33
+ - 新增组合式 `TftpReceipt` 与 `tftp_backup_receipt`,保留原 `TftpBackup` 三成员接口,明确配置来源、格式及 requested/actual 路径;默认仍是 device_reported,没有服务器摘要。已确认上传后的日志/清理失败携带安全回执,Fleet 标为 reported_with_error;实际路径异常单独诊断,不自动重传。
34
+ - TFTP 批量示例在实际路径未确认时停止服务器文件核验,避免把计划文件名对应的其他备份误报为本次上传结果。
35
+ - 拓扑描述改写按“修改并退出视图 → 读回 → 保存”执行,读回不匹配、解析不完整或查询与审批不同均不保存。计划命令新增读回序列,旧计划必须重新生成;完成步骤跨阶段保留,stale_plan 的写入前异常契约不变。
36
+ - 保存需要明确的设备完成消息,超时或缺少证据返回 persistence_unconfirmed,不自动重放或回滚。现有会话租约覆盖全部阶段及间隙,未实现分阶段契约的自定义策略暂只允许读取。
37
+ - PAN-OS 自动描述计划及改写在 I/O 前以 candidate_isolation_unavailable 拒绝,能力查询返回 false;待目标固件的候选归属、锁与提交协议通过实验后再开启,只读邻居与描述查询保留。
38
+ - 本地备份在采集前获取跨实例/进程的私有路径锁,默认争用返回 `backup_busy`,可显式有限等待。统一目录、大小写和 Unicode 别名;锁文件长期保留。锁必须在会话租约外获取,已有租约内调用 `backup` 会返回 `SessionBusy`。
39
+ - 旧备份比较和离线读取通过同一个 `NOFOLLOW` 文件描述符检查类型与读取;保留直接备份安全替换末级符号链接、Fleet/离线入口拒绝符号链接的原有区别。
40
+ - 私有写入增加父目录同步及阶段回执。替换后失败保留已知产物,Fleet 标为部分成功,报告保留已提交位置;目录同步不支持单独报错。`Backup`/`Outcome`/`Batch` 成员、默认 JSON 和 strict 成功规则保持不变。
41
+ - Netdisco 清单增加单响应/累计字节、设备数和总期限预算,认证、分页与兼容查询共享计数;默认 HTTP 在下载过程中检查并关闭超额连接,失败不执行部分清单。旧 requester 回调保留,阻塞由调用方负责;HTTP 默认兼容,新增显式禁止明文的配置。
42
+ - 批次开始时冻结非敏感设置,执行中 ENV 修改只影响新批次,设备凭据仍逐台动态解析。集中预检协议、主机密钥、日志、采样和清单预算;CLI > ENV > YAML > 默认值的优先级明确,离线导出不要求清单配置。自定义凭据解析器仍可显式覆盖连接默认值。
43
+ - 复用 expect-pty 的公共 `Expect::Redactor`,移除连接器重复的字节匹配、重叠合并和分片算法,只保留秘密作用域与输出敏感性策略。依赖要求调整为 `~> 0.5.0`,使用已正式发布的公共接口。
44
+ - 配置采集默认将响应正文视为敏感数据:所有厂商的采集、候选差异、追加查询及结果清理异常均不把配置正文传入 text/debug/raw 日志、外部 logger 或错误诊断。`running_config.value!`、已完成步骤与本地备份保留原始业务内容。
45
+ - `Command` 增加独立的 `output_sensitive: false` 标记和 `output_sensitive?` 查询,`with_text` 保留该标记。手写采集命令可显式启用;包含秘密的命令文本仍需 `sensitive: true`。敏感范围结束后恢复普通命令诊断,不长期登记配置正文。
46
+
47
+ - 核心引擎、脱敏与错误处理(含 `Redactor`)、批量并发分别要求行和分支覆盖率均达到 80%;关键文件未测量时失败,并列出全部未加载库文件。测试、CI 与发布预检共用门槛。
48
+ - 在 `engine/` 和 `netdisco/` 启用方法长度 40、ABC 复杂度 60 的检查;拆分响应读取、CLI、清单规则校验和工作线程的职责。
49
+ - 补充连接恢复、传输参数、终端控制字符、覆盖率退出状态等边界测试,统一会话错误输出的 4096 字节截断逻辑,保持先脱敏后截断。
50
+ - TFTP 文件名在字节截断前明确拒绝非 ASCII 清单标签,保留合法 ASCII 路径和原有长度限制。
51
+ - 新增安全上报及贡献指南,并随 gem 分发;记录关键依赖的维护核对、替代方案边界及厂商、模板、调度等候选工作。
52
+
3
53
  ## 0.4.1 - 2026-09-27
4
54
 
5
55
  - 用 Ruby 标准库 `Logger` 替代 ActiveSupport 日志依赖,保留设备标记、级别、脱敏和注入日志器的所有权。
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,40 @@
1
+ # 参与开发
2
+
3
+ 请先阅读 [README](README.md) 和[架构文档](docs/architecture.md),在源码检出目录中开发。问题和 PR 可使用中文或英文;涉及凭据泄露或可利用漏洞时,使用 [SECURITY.md](SECURITY.md) 中的私密渠道。
4
+
5
+ ## 本地检查
6
+
7
+ 需要 Ruby 3.2 及以上版本、POSIX 环境和 Git。安装依赖后运行完整预检:
8
+
9
+ ```sh
10
+ bundle install
11
+ script/ci
12
+ ```
13
+
14
+ 也可使用 `bundle exec rake release:check`;它执行同样的检查,不上传发布包。首次运行需要联网下载依赖、Gitleaks 和 actionlint。检查包含敏感数据扫描、RuboCop、工作流校验、测试、gem 白名单与隔离安装,以及本地 PTY 烟测。它不会连接真实设备。
15
+
16
+ 开发中可直接运行一个测试文件:
17
+
18
+ ```sh
19
+ bundle exec ruby -Ilib -Itest test/engine_reliability_test.rb
20
+ bundle exec rake lint
21
+ ```
22
+
23
+ 提交前执行完整的 `bundle exec rake test` 或 `script/ci`。完整测试会检查核心引擎、脱敏与错误处理、批量工作线程各组的行和分支覆盖率,两项均不得低于 80%;任何关键文件没有覆盖率数据也会失败。只运行一个文件不能代替这个门槛,详细口径见[验证文档](docs/VERIFICATION.md)。
24
+
25
+ ## 代码与测试约定
26
+
27
+ - `engine/` 与 `netdisco/` 启用 `Metrics/MethodLength`(40)和 `Metrics/AbcSize`(60)。超限时按职责拆分,不添加目录豁免或提高阈值来掩盖增长。
28
+ - 复用现有 Ruby 对象、不可变档案和厂商策略。复杂的资源所有权、锁、截止时间及脱敏生命周期继续用中文注释说明原因。
29
+ - 测试应证明业务行为:失败不覆盖备份、中断释放全部资源、命令不自动重放、错误不泄露凭据。并发测试用队列等同步手段控制执行顺序。
30
+ - 使用虚构凭据和 `192.0.2.0/24` 等文档地址。不要提交设备配置、备份、私有地址或日志;扫描规则同样适用于测试和示例。
31
+ - 行为或公开契约变更写入 `CHANGELOG.md` 的 `Unreleased`,同步修改相应文档。保留与本次任务无关的工作区改动。
32
+
33
+ ## 增加厂商或模板
34
+
35
+ 1. 在 `lib/net/connector/vendor/<厂商>.rb` 声明提示符、命令、交互及策略绑定,并在设备注册入口登记厂商键。已有规则可直接复用,差异逻辑放在该厂商目录中。
36
+ 2. 只声明实际实现并经过验证的能力。配置采集覆盖登录、分页、完整提示符、失败响应、空配置和视图恢复;TFTP 需要明确的上传完成证据;拓扑变更还需要计划重验与回读。
37
+ 3. 新 TextFSM 模板放入 `lib/net/connector/templates/`,按需更新 `index`。提供脱敏的正常、空表、畸形与部分输出样本,验证字段和完整性判断;注明样本的设备系列、固件及来源许可。
38
+ 4. 补齐对应测试及能力矩阵,运行完整预检,确认模板进入实际构建的 gem。现场验证应在 PR 中注明环境和结果,模拟传输测试不能代替现场证据。
39
+
40
+ PR 请说明具体问题、修改后的行为、验证命令及仍未验证的范围。仓库的 PR 模板和[发布文档](docs/RELEASING.md)提供交付约定。
data/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  `net-connector` 通过 SSH 或 Telnet 操作网络设备的命令行。它可以采集运行配置、保存私有备份、执行命令脚本、回答设备提示、记录脱敏会话日志,并在失败时返回结构化错误和已完成的步骤。
4
4
 
5
- 代码按职责组织:`engine/` 管理会话、传输、脚本、结果和日志;`device/` 提供设备入口、档案、运行配置和接口名称处理;`vendor/<厂商>/` 保存各厂商的采集、TFTP 和拓扑规则;`operations/` 实现公共业务流程;`netdisco/` 负责清单和批量编排。相同规则直接复用,设计说明见[架构文档](docs/architecture.md)。用 `require "net/connector"` 加载设备 API,用 `require "net/connector/netdisco"` 加载 Netdisco 集成;厂商规则和 TextFSM 解析器按需加载。
5
+ 代码按职责组织:`engine/` 管理会话、传输、脚本、结果和日志;`device/` 集中设备入口、档案、配置采集/保存、备份和拓扑能力;`vendor/<厂商>/` 保存厂商差异;`storage/` 负责私有文件、路径锁和离线配置;`textfsm.rb` 提供唯一的 TextFSM 适配入口;`netdisco/` 负责清单和批量编排。每项设备能力的公开方法与实现放在一起,由 `Base` 组合,设计说明见[架构文档](docs/architecture.md)。用 `require "net/connector"` 加载设备 API,用 `require "net/connector/netdisco"` 加载 Netdisco 集成;厂商规则和 TextFSM 依赖按需加载。
6
6
 
7
- 支持 Ruby 3.2 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。主要依赖为 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.3.x(至少 0.3.1)和 [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.x。
7
+ 支持 Ruby 3.2 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。主要依赖为 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.5.x 和 [`textfsm`](https://rubygems.org/gems/textfsm) 0.2.x。
8
+
9
+ 脱敏直接复用 expect-pty 从 0.5.0 起公开的 `Expect::Redactor` 接口;连接器只管理秘密作用域和配置输出的隐私策略。
8
10
 
9
11
  ## 安装
10
12
 
@@ -31,7 +33,7 @@ require "net/connector"
31
33
  | `:huawei` | 华为 | `dis cur` | `save force` |
32
34
  | `:hillstone` | 山石 StoneOS | `show configuration running` | `save all` |
33
35
 
34
- 兼容标识 `:cisco_n9k` 和 `:paloalto`。厂商连接器位于 `Net::Connector` 下,例如 `Net::Connector::H3cWireless::Connector`。
36
+ 厂商标识仅使用上表的规范名称。厂商连接器位于 `Net::Connector` 下,例如 `Net::Connector::H3cWireless::Connector`。
35
37
 
36
38
  ## 登录与本地备份
37
39
 
@@ -45,7 +47,11 @@ Net::Connector.open(:cisco_ios,
45
47
  end
46
48
  ```
47
49
 
48
- `open` 会在代码块结束或出错时关闭会话。`backup` 先采集配置,再以 `0600` 权限原子替换目标文件;采集失败不会覆盖旧备份。返回值为 `Backup(path:, bytes:, sha256:, collected_at:)`。调用方应创建并保护备份目录,运行配置可能含有设备凭据。
50
+ `open` 会在代码块结束或出错时关闭会话。`backup` 在采集前取得目标路径锁,持有到比较、保存和生成原有 `Backup` 元数据对象完成;采集失败不会覆盖旧备份。同路径争用默认立即抛出 `BackupBusy`(`code: :backup_busy`),需要有限等待可传 `lock_timeout: 2`,单位为秒。直接调用 `backup`;不要在已有 `with_operation` 会话租约内调用,否则会因锁顺序返回 `SessionBusy`。
51
+
52
+ 写入使用 `0600` 临时文件、文件同步、原子替换及父目录同步。替换后同步或收尾失败会抛出 `BackupPersistenceError`,`error.backup` 保留已写文件的元数据,`error.receipt` 区分 `:committed`(持久性未确认)和 `:durable`(同步已完成)。目录同步不受支持对应 `:backup_durability_unsupported`,不会静默报告持久化成功,也不会自动重做设备采集。
53
+
54
+ 调用方应创建并保护备份目录,运行配置可能含有设备凭据。同目录下的 `.net-connector-<摘要>.lock` 是私有的长期锁文件,正常释放不删除它。所有写入者须使用本库的锁协议;`NOFOLLOW` 不保护被外部替换的祖先目录。锁名保守合并大小写及 Unicode 等价写法,实际备份文件名不变;在区分大小写的文件系统上,这些名称也会串行。
49
55
 
50
56
  ## 设备发起的 TFTP 备份
51
57
 
@@ -61,12 +67,22 @@ end
61
67
 
62
68
  示例会下发 `tftp 192.0.2.30 put flash:/startup.cfg`;可传入 `path: "site/switch.cfg"` 指定目标文件名。H3C 默认通过 `display startup` 查找保存配置;华为因型号差异需要显式指定 `source_file:`。Cisco IOS 使用交互式 `copy running-config tftp:`;Nexus 9000 默认使用 `vrf management`,可用 `vrf:` 覆盖。山石导出已保存的启动配置,Radware Alteon 生成 `.tgz` 并处理私钥及 `mansync` 提示,PAN-OS 使用固定的 `running-config.xml` 文件名。其他厂商默认使用 `<管理地址>.cfg`。
63
69
 
64
- 只有设备回显确认传输完成,方法才返回 `TftpBackup(server:, path:, completed_at:)`。明确失败对应 `:transfer_failed`,缺少成功证据对应 `:transfer_unconfirmed`。本方法不读取服务器上的文件。TFTP 不加密配置数据,应限制在合适的管理网络中使用。
70
+ 只有设备回显确认传输完成,`tftp_backup` 才返回不可变的 `TftpReceipt`。回执直接包含 `server`、实际目标 `path`、`completed_at`、`configuration_kind`、`source_file`、`format`、`requested_path`、`verification` 和 `server_sha256`。明确失败对应 `:transfer_failed`,缺少成功证据对应 `:transfer_unconfirmed`。本方法不读取服务器上的文件。TFTP 不加密配置数据,应限制在合适的管理网络中使用。
71
+
72
+ 回执的当前验证等级为 `:device_reported`,服务器摘要为 `nil`。H3C 自动探测结果标为 `:startup`;H3C/华为显式文件标为 `:saved_file`,格式为 `:unknown`,不凭扩展名推断内容。山石未指定文件名时 `requested_path` 为 `nil`,`path` 保留设备生成名称。
73
+
74
+ 策略先校验参数组合,再在同一次会话租约中完成源探测、上传、证据检查和收尾。上传已确认但日志或清理失败时抛出 `TftpCompletionError`,通过 `error.receipt` 保留完成事实。显式请求与实际路径不同返回 `:transfer_path_mismatch`;实际路径无法安全确认返回 `:transfer_path_unconfirmed`,此时回执的 `path` 为 `nil`。这些错误不会自动重传;Fleet 保留上传事实并标为 `reported_with_error`。
65
75
 
66
76
  可运行[单设备示例](examples/tftp_backup.rb)。它从环境变量读取 `DEVICE_VENDOR`、`DEVICE_HOST`、`DEVICE_USERNAME`、`DEVICE_PASSWORD` 和 `TFTP_HOST`;`TFTP_SOURCE_FILE`、`TFTP_PATH`、`TFTP_VRF` 分别指定源文件、目标文件和设备 VRF。
67
77
 
68
78
  只需在内存中采集配置时,调用 `device.running_config`;它返回 `Result`,`result.value!` 返回清理后的文本,失败时抛出对应错误。
69
79
 
80
+ 配置采集统一由 `device/running_config` 提供,厂商差异位于 `vendor/<厂商>/running_config`。
81
+ 旧 `operations/running_config`、TFTP / 拓扑厂商转发路径及 `engine/base` 等设备转发入口已移除;
82
+ 自定义扩展请按[加载入口与迁移表](docs/architecture.md#加载入口与厂商策略)使用当前路径和常量。
83
+
84
+ 配置采集默认屏蔽日志和错误诊断中的配置正文,包括 debug/raw 日志和外部 logger;返回的配置、步骤输出和备份内容保持完整。调用方应按敏感数据保管这些业务结果。
85
+
70
86
  ## TextFSM 解析
71
87
 
72
88
  `parse_command` 执行一条命令,再按厂商和命令选择 TextFSM 模板。内置索引覆盖 Cisco IOS 的 `show ip interface brief`,以及拓扑发现使用的 CDP/LLDP 命令:
@@ -88,10 +104,12 @@ interfaces = device.parse_config(template: "cisco_ios_running_config_interfaces.
88
104
  两种方法都返回由模板字段名组成的哈希数组;匹配不到记录时返回 `[]`。模板缺失或无效会抛出 `ParsingError`,设备命令失败则保留原始连接器错误。可用 `template:` 指定外部模板,或用 `template_dir:` 指定含 `index` 的模板目录。每次解析使用独立解析器,批量任务之间不共享状态。已有本地备份也可离线解析:
89
105
 
90
106
  ```ruby
91
- saved = Net::Connector::Operations::SavedConfig.new(directory: "/var/backups")
107
+ saved = Net::Connector::Storage::SavedConfig.new(directory: "/var/backups")
92
108
  rows = saved.parse(host: "192.0.2.10", template: "/path/to/template.textfsm")
93
109
  ```
94
110
 
111
+ 解析默认严格检查 UTF-8 字节,包括终端控制符处理前的原文和处理后的文本;非法字节会抛出 `ParsingError`(`code: :invalid_output_encoding`),不会转成替换文本继续生成记录或拓扑计划。库不猜测或自动转换其他编码,原始结果、备份和离线导出保持原字节。Netdisco 入口及纯文件导出不加载 TextFSM,实际解析时才加载。
112
+
95
113
  ## 邻居发现与接口描述
96
114
 
97
115
  `neighbors` 在 Cisco IOS/NX-OS 上查询 CDP,在 H3C、H3C 无线、山石和 PAN-OS 上查询 LLDP。记录包含 `local_interface`、`neighbor_name`、`neighbor_interface`、`chassis_id` 和 `protocol`。H3C 会按表头选择不同列顺序的模板;未知输出会抛出 `ParsingError`,不会误判为空表。`interface_descriptions` 从运行配置读取当前描述,Radware 则从配置转储读取端口名称。
@@ -110,7 +128,11 @@ end
110
128
 
111
129
  默认描述为 `To <邻居名称> <邻居接口简称>`。接口缩写默认开启并保留大小写:`ethernet1/1` 变为 `eth1/1`,`Ethernet1/1` 变为 `Eth1/1`,`GigabitEthernet1/0/1` 变为 `Gi1/0/1`;未知形式保持原样。原始邻居记录、计划证据和本机下发接口名不会被缩写。`abbreviate: false` 关闭缩写,`lowercase: true` 才会转为小写;也可传入代码块自行生成完整描述。公共纯函数是 `Net::Connector::InterfaceDescription.format`。
112
130
 
113
- 规划会拒绝歧义邻居、缺少对端身份、不安全文本和无法识别的输出。下发必须传 `confirmed: true`,操作会再次读取邻居和旧描述;证据变化返回 `:stale_plan`。计划包含厂商保存命令,PAN-OS 使用 `commit`。执行后还会回读配置,未确认新描述时返回 `:description_unconfirmed`。脚本失败的 `Result` 保留已完成步骤。Radware 支持读取端口名称,但当前不提供邻居发现或自动改写;其 `neighbors` 返回 `:neighbor_discovery_unsupported`。现场下发前应按设备固件核对命令与回显。
131
+ 规划会拒绝歧义邻居、缺少对端身份、不安全文本和无法识别的输出。下发必须传 `confirmed: true`,操作会再次读取邻居和旧描述;证据变化在写入前抛出 `DeviceError`,其 `code` 为 `:stale_plan`。IOS/NX-OS、H3C 和山石采用“修改 → 退出配置视图 → 读回 → 保存”的顺序;读回不匹配返回 `:description_unconfirmed`,解析不完整或查询命令不符合审批时也不会保存。
132
+
133
+ `plan.commands` 现在包括读回命令,旧版计划必须重新生成、审核;执行仍返回 `Result`,保留修改、读回及保存阶段已经完成的步骤。只有设备提供明确保存完成行才成功,保存超时、失败或只有提示符会返回 `:persistence_unconfirmed`,此时描述可能已生效,不能自动重放或回滚。现场下发前仍应按设备固件核对命令与回显。
134
+
135
+ PAN-OS 的自动描述计划及改写暂不提供,`supports?(:interface_description_changes)` 为 false,入口在设备 I/O 前抛出 `:candidate_isolation_unavailable`。候选配置的归属、验证及提交需要经目标固件实验确认的独立流程;目前可继续只读查询邻居和描述。Radware 支持读取端口名称,但不提供邻居发现或自动改写,其 `neighbors` 返回 `:neighbor_discovery_unsupported`。
114
136
 
115
137
  PAN-OS 在导出前后检查候选配置差异,并拒绝 XML 或非 set 格式输出,避免将未提交配置误作运行配置。
116
138
 
@@ -135,13 +157,37 @@ if result.failure?
135
157
  end
136
158
  ```
137
159
 
138
- `execute` 执行一条命令;`execute_script` 接收 `Script` 或命令数组;`Script.load(path)` 读取脚本文件。所有命令在设备 I/O 前校验,后续步骤失败时仍保留已完成结果,库不会自动重放命令。`save_config` 显式执行厂商保存命令,普通脚本不会自动保存。
160
+ `execute_command` 执行一条命令;`execute_script` 接收 `Script` 或命令数组;`Script.load(path)` 读取脚本文件。所有命令在设备 I/O 前校验,后续步骤失败时仍保留已完成结果,库不会自动重放命令。`save_config` 显式执行厂商保存命令,普通脚本不会自动保存。
161
+
162
+ 创建连接器时可设置 `max_script_output_bytes: 8 * 1024 * 1024`,限制每个脚本及其追加查询累计收到的原始响应字节;默认 `nil` 保持原有完整输出行为。达到上限后不再发送下一命令,当前响应超过上限时保留刚完成的步骤并返回 `ScriptOutputLimitExceeded`(`code: :script_output_limit_exceeded`)。命令可能已经执行,不会自动重试。原有 `max_output_bytes` 继续限制单次读取响应;累计预算不是进程内存上限,详细计数规则见架构文档。
139
163
 
140
- 厂商档案处理分页和常见确认提示。特定命令可给 `execute` 传入 `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]`。敏感命令及交互会暂停回显日志并在错误中脱敏;若普通命令文本包含秘密,必须显式标记 `sensitive: true`。
164
+ 厂商档案处理分页和常见确认提示。特定命令可给 `execute_command` 传入 `interactions: [Net::Connector::Interaction.new(/Token:\z/, ->(_) { "value\n" }, sensitive: true)]`。敏感命令及交互会暂停回显日志并在错误中脱敏;若普通命令文本包含秘密,必须显式标记 `sensitive: true`。
165
+
166
+ 命令文本安全、输出可能包含秘密时,使用 `device.execute_command("show running-config", output_sensitive: true)`。该标记保护响应及其准备、后处理、回调异常,保留安全命令文字;不会改写 `Result` 的业务输出。`running_config` 自动启用此保护,手写脚本的默认值仍为 `false`。
141
167
 
142
168
  ## 连接与日志设置
143
169
 
144
- `Configuration` 支持 `protocol: :ssh`(默认)或 `:telnet`,以及端口、超时、输出大小、`log_file`、`logger`、`log_format`、`log_level` 和 `known_hosts` 等参数。文本日志使用 Ruby 标准库 `Logger`,记录本地时间、级别、设备标记和中文事件。`:info` 记录连接、登录、命令和 TFTP 结果;`:debug` 还记录脱敏回显与耗时;`:warn`、`:error` 只保留相应级别。`:raw` 文件只写设备字节,不写事件元数据。可注入 `logger: Rails.logger`,连接器不会关闭或修改调用方的日志器。
170
+ `Configuration` 支持 `protocol: :ssh`(默认)或 `:telnet`,以及端口、超时、输出大小、`log_file`、`logger`、`log_format`、`log_level` 和 `known_hosts` 等参数。文本日志使用 Ruby 标准库 `Logger`,记录毫秒时间、级别、设备、中文说明和完整事件字段。`:info` 包括连接、登录、命令响应、脚本处理和 TFTP 结果;`:debug` 增加逐行脱敏回显;`:warn`、`:error` 只保留相应级别。`:raw` 文件只写经过现有敏感保护的设备字节,不添加事件字段。
171
+
172
+ 每次连接生成 `session_id`,每条实际发送的命令分配 `command_id`;日志还包含 `operation`、`phase`、脚本 `source` / `line`、`duration_ms`、`response_bytes` 和失败 `code`。`command_complete` 的 `response_received` 只表示收到了提示符;`operation_complete` 覆盖脚本准备、执行及后处理,不替代 TFTP 服务端核验或设备持久化证据。普通自定义事件使用 `device.log_event("audit", level: :info, count: 2)`。
173
+
174
+ 可注入 `logger: Rails.logger` 或普通 `Logger`。有效级别取 `log_level` 与调用方**当前**级别中较严格的一项;连接器不修改它的级别、formatter 或 progname,也不关闭它。消息是已脱敏且冻结的 `Net::Connector::Log::Event`,`to_s` 供文本显示,`to_h` 供应用 formatter 输出 JSON:
175
+
176
+ ```ruby
177
+ require "net/connector"
178
+ require "json"
179
+ require "logger"
180
+ require "time"
181
+
182
+ logger = Logger.new($stdout)
183
+ logger.formatter = lambda do |severity, time, program, message|
184
+ fields = message.is_a?(Net::Connector::Log::Event) ? message.to_h : { message: message.to_s }
185
+ "#{JSON.generate(time: time.iso8601(3), severity: severity, program: program, **fields)}\n"
186
+ end
187
+ # 将 logger: logger 传给 Net::Connector.build / open。
188
+ ```
189
+
190
+ 事件字段接受字符串、符号、整数、有限浮点数、布尔和 nil;复杂对象统一隐藏,不展开对象内容。事件名、字段名和值经过校验/脱敏,会话与命令标识不可由自定义字段覆盖。敏感命令或配置处理期间,自定义事件的名称和载荷整体隐藏,避免钩子把未登记的配置秘密写进日志。更多边界见[架构文档](docs/architecture.md)。
145
191
 
146
192
  主机密钥策略默认为 `:strict`;`:accept_new` 接受首次连接的密钥;`:replace` 需要显式 `known_hosts` 文件。`telnet_fallback` 和 `legacy_ssh` 默认关闭,只在已识别的连接失败时使用。外部命令以参数数组执行,不经 shell。Telnet 不提供 SSH 加密,只应在可信管理网络启用。设备授权和变更审批由调用方负责。
147
193
 
@@ -149,11 +195,19 @@ end
149
195
 
150
196
  `Net::Connector::Netdisco` 读取并验证完整清单,再将支持的记录映射到连接器,使用有上限的工作线程执行备份。Netdisco 只提供清单字段;设备凭据来自环境变量或调用方提供的解析器。清单会在连接任何设备前完成校验;不支持、被过滤、重复、缺少凭据、失败,以及保存成功但关闭失败的结果分别保留。
151
197
 
152
- `Fleet#plan_backup` 和 `Fleet#plan_tftp_backup` 从同一份清单生成计划。把计划传给 `backup_all(plan:)` 或 `tftp_backup_all(plan:)`,可使预览与执行选择同一批设备;计划与清单不符时会拒绝执行。单台设备异常或结果回调失败不会阻止其他设备。`batch.summary` 包含总数、成功、失败、部分成功、跳过、具体状态和逐台结果。部分成功表示设备已报告备份完成,但会话关闭失败。TFTP 的 `reported_uploaded` 仅代表设备报告上传,不代表服务器文件已核验。
198
+ 每次 `Client#devices` 默认限制单响应 16 MiB、累计响应 128 MiB、去重前 100,000 条记录、10,000 页和 300 秒总期限。认证、分页及兼容查询共用这些预算;默认 HTTP 客户端逐块计数,超限会关闭连接并抛出带稳定 `code` 的 `Client::Error`,Fleet 不会执行半份清单。这些默认值是可调整的设计起点,不是实测容量。可注入 `requester: ->(uri, request)`;该回调只能在回调返回后检查正文和期限,回调自身的阻塞及内存用量由注入方控制。
199
+
200
+ 推荐使用 HTTPS,标准证书验证保持开启。`allow_insecure_http` 默认为 `true`;设为 `false` 可在发请求前拒绝 HTTP,ENV 中对应 `NETDISCO_ALLOW_INSECURE_HTTP=false`。HTTP 会明文传输登录凭据和 API key,迁移时应先提供可验证的 HTTPS 端点。
153
201
 
154
- 本地 `backup(path:)` 用 SHA-256 比较新旧配置,`backup.change` 返回 `:created`、`:changed` 或 `:unchanged`;未变化文件保留修改时间。`backup_all` 的 `on_change:` 仅在新建或更改文件保存后触发;`on_start:` 和 `on_result:` 观察每台已尝试设备。回调异常记录在 `batch.callback_errors`,不丢弃设备结果。每项结果包含开始、结束和耗时。TFTP 无法比较服务器文件,因此没有 `change`,也不触发变更通知。
202
+ `Fleet#plan_backup` 和 `Fleet#plan_tftp_backup` 从同一份清单生成计划。把计划传给 `backup_all(plan:)` 或 `tftp_backup_all(plan:)`,可使预览与执行选择同一批设备;计划与清单不符时会拒绝执行。单台设备异常或结果回调失败不会阻止其他设备。`batch.summary` 包含总数、成功、失败、部分成功、跳过、具体状态和逐台结果。部分成功包括已保存但关闭失败,以及本地文件已替换但目录同步或收尾失败;后者保留 backup 并标为 `saved_with_error`。TFTP 的 `reported_uploaded` 仅代表设备报告上传,不代表服务器文件已核验。
155
203
 
156
- 每批默认写入私有 JSON 报告,路径见 `batch.report_location`。调用方如有数据库仓储,可传 `ResultStore::Database.new(repository: YourModel)`;仓储需实现 `create!(attributes)`。`result_store: nil` 表示由调用方自行持久化。报告失败保留在 `batch.report_error`,同时使 `batch.success?` 为假。
204
+ 本地 `backup(path:)` 用 SHA-256 比较新旧配置,`backup.change` 返回 `:created`、`:changed` 或 `:unchanged`;内容未变且权限、文件身份正常时保留修改时间。这不补验历史写入的断电持久性。`backup_all` 的 `on_change:` 仅在新建或更改文件保存后触发;`on_start:` 和 `on_result:` 观察每台已尝试设备。回调异常记录在 `batch.callback_errors`,不丢弃设备结果。每项结果包含开始、结束和耗时。TFTP 无法比较服务器文件,因此没有 `change`,也不触发变更通知。
205
+
206
+ 每批默认写入私有 JSON 报告,路径见 `batch.report_location`。调用方如有数据库仓储,可传 `ResultStore::Database.new(repository: YourModel)`;仓储需实现 `create!(attributes)`。`result_store: nil` 表示由调用方自行持久化。报告失败保留在 `batch.report_error`,同时使 `batch.success?` 为假。若报告已替换但目录同步失败,仍保留位置;离线 `--export --output` 遇到同类错误返回 2,并说明文件已经提交。
207
+
208
+ Fleet 统一返回 `Netdisco::Report`,JSON 的 `schema_version` 固定为 `2`。报告包含 `policy`、`policy_success`、清单覆盖和受控诊断;`success?` / `status` 表示严格完成情况,`policy_success?` 表示所选成功策略。任务耗时使用单调时钟,UTC 开始/结束时间独立保留。`report.batch` 是原始执行快照;手工构造的 Batch 可用 `batch.build_report(policy: :selected)` 生成报告,不会再次执行设备或重写文件。
209
+
210
+ `success_policy: :selected` 要求至少一台设备成功,其他记录只因 `filtered` 或 `sample_limit` 跳过,而且没有部分成功、回调或报告错误。缺少凭据、重复地址、无效地址、未知厂商、目标冲突和未知状态均会阻止成功。`coverage.complete` 只表示每条清单记录都已尝试任务;失败任务也计入尝试,不能据此判断配置已保存。自定义 `ResultStore#write(report, directory:)` 始终接收 Report,并通过 `summary` 获取统一 JSON 结构。
157
211
 
158
212
  ```sh
159
213
  export NETDISCO_URL=https://netdisco.example/netdisco
@@ -189,6 +243,12 @@ exit 1 unless batch.success?
189
243
  netdisco:
190
244
  url: https://netdisco.example/netdisco
191
245
  page_size: 500
246
+ max_response_bytes: 16777216
247
+ max_inventory_bytes: 134217728
248
+ max_devices: 100000
249
+ max_pages: 10000
250
+ inventory_timeout: 300
251
+ allow_insecure_http: false
192
252
  backup:
193
253
  directory: /var/backups/network
194
254
  concurrency: 4
@@ -198,6 +258,7 @@ inventory:
198
258
  192.0.2.7: h3c_wireless
199
259
  ssh:
200
260
  host_key_policy: strict
261
+ max_script_output_bytes: null # 可选正整数字节数;null 保持不限制累计值。
201
262
  tftp:
202
263
  server: 192.0.2.10
203
264
  vrfs:
@@ -213,9 +274,11 @@ net-connector-backup --config config.yml --tftp --all
213
274
  net-connector-backup --config config.yml --export 192.0.2.7 --output ./exports/device.cfg
214
275
  ```
215
276
 
216
- `--plan` 只拉取并验证清单;`--host` 选择一个管理地址;`--tftp` 默认每厂商最多选择五台,`--all` 选择所有就绪设备。本地备份默认选择所有就绪设备,可用 `--limit-per-vendor` 限制。`--show-config` 只输出有效的非敏感设置,不访问 Netdisco。CLI 会拒绝未知 YAML 字段、Ruby 对象标签及配置中的凭据。`--export IP` 离线读取已有 `<IP>.txt`,或唯一匹配的旧版 `<设备名>-<IP>.txt`;默认原样写到标准输出,指定 `--output` 后以 `0600` 权限原子写文件。导出的配置仍是敏感数据。
277
+ `--plan` 只拉取并验证清单;`--host` 选择一个管理地址;`--tftp` 默认每厂商最多选择五台,`--all` 选择所有就绪设备。本地备份默认选择所有就绪设备,可用 `--limit-per-vendor` 限制。`--show-config` 只输出有效的非敏感设置,不访问 Netdisco。CLI 会拒绝未知 YAML 字段、Ruby 对象标签及配置中的凭据。`--export IP` 只离线读取规范化管理地址对应的 `<IP>.txt`;默认原样写到标准输出,指定 `--output` 后以 `0600` 权限原子写文件。导出的配置仍是敏感数据。
278
+
279
+ CLI 的计划与批次摘要使用 JSON。默认 `--success-policy strict` 使用严格规则:非空清单且全部成功、回调及报告正常时为 `0`;空清单或有跳过、部分成功、失败时为 `1`;清单或配置错误为 `2`。`--host` 未在清单中找到也返回 `2`。由于其他清单记录会标记为过滤,默认单主机备份成功时批次退出码仍可能是 `1`;应查看 JSON 中的 `succeeded`、`skipped` 和逐台 `status`。
217
280
 
218
- CLI 的计划与批次摘要使用 JSON。非空清单且全部成功时退出码为 `0`;空清单或有跳过、部分成功、失败时为 `1`;清单或配置错误为 `2`。`--host` 未在清单中找到也返回 `2`。由于其他清单记录会标记为过滤,单主机备份成功时批次退出码仍可能是 `1`;应查看 JSON 中的 `succeeded`、`skipped` 和逐台 `status`。
281
+ 显式使用 `--success-policy selected` 后,CLI 按上述 selected 规则决定退出码,保留严格的 `status: incomplete` 与跳过计数,另列 `policy_success`。例如 `net-connector-backup --config config.yml --host 192.0.2.7 --success-policy selected`。成功策略由本次 CLI/API 参数指定,不改变已批准的清单选择,也不触发重试。
219
282
 
220
283
  小范围现场试运行可用[本地批量示例](examples/netdisco_backup.rb),默认每厂商最多三台;`NET_CONNECTOR_SAMPLE_PER_VENDOR` 可设为 1 至 5。设备发起 TFTP 上传可用[批量 TFTP 示例](examples/netdisco_tftp_backup.rb),默认每厂商最多五台;`NET_CONNECTOR_ALL=1` 才选择全部就绪设备,全量任务默认并发 50。`NET_CONNECTOR_CONCURRENCY` 可覆盖并发数。两类示例将结果和日志写入唯一的 `examples/backups/<UTC 时间戳>-<后缀>/` 目录,该目录不纳入 Git。
221
284
 
@@ -223,7 +286,8 @@ CLI 的计划与批次摘要使用 JSON。非空清单且全部成功时退出
223
286
 
224
287
  批量 TFTP 可用 `NET_CONNECTOR_<VENDOR>_TFTP_SOURCE_FILE` 指定单厂商源文件。`NET_CONNECTOR_H3C_TFTP_SOURCE_FILE` 与 `NET_CONNECTOR_H3C_WIRELESS_TFTP_SOURCE_FILE` 可分别覆盖 H3C 设备的自动发现结果;华为使用 `NET_CONNECTOR_HUAWEI_TFTP_SOURCE_FILE`。源文件是设备上的路径,需符合连接器校验规则。
225
288
 
226
- 本地配置备份写到 `<目录>/<IP>.txt`,IPv6 的 `:` 转成 `_`。设备改名不改变文件名或比较基线。若规范文件不存在,唯一匹配的旧版 `<设备名>-<IP>.txt` 可作比较基线但不会被改写;匹配多个旧文件时会明确失败。规范文件优先,符号链接和非普通文件会被拒绝。文件原子替换为 `0600`,新目录权限为 `0700`。批次在当前进程执行,需要定时任务或持久队列时由调用方安排;失败命令不会自动重试。
289
+ 本地配置备份写到 `<目录>/<IP>.txt`,IPv6 的 `:` 转成 `_`。设备改名不改变文件名或比较基线;只读取该规范路径,缺失时创建新备份。其他名称的文件不参与查找或哈希比较。Fleet 和离线读取拒绝符号链接及非普通文件。文件原子替换为 `0600`,新目录权限为 `0700`。批次在当前进程执行,需要定时任务或持久队列时由调用方安排;失败命令不会自动重试。
290
+
227
291
 
228
292
  | 环境变量 | 默认值 | 用途 |
229
293
  | --- | --- | --- |
@@ -232,10 +296,17 @@ CLI 的计划与批次摘要使用 JSON。非空清单且全部成功时退出
232
296
  | `NETDISCO_USERNAME`, `NETDISCO_PASSWORD` | 未提供 API 密钥时必填 | 清单 API 登录 |
233
297
  | `NETDISCO_API_KEY` | 未设置 | 直接使用已有 API 密钥 |
234
298
  | `NETDISCO_PAGE_SIZE` | `500` | 清单分页大小 |
299
+ | `NETDISCO_MAX_PAGES` | `10000` | 最大分页次数 |
300
+ | `NETDISCO_MAX_RESPONSE_BYTES` | `16777216` | 单次响应正文上限,认证和错误正文也计数 |
301
+ | `NETDISCO_MAX_INVENTORY_BYTES` | `134217728` | 一次清单调用的累计正文上限 |
302
+ | `NETDISCO_MAX_DEVICES` | `100000` | 去重前累计记录上限,兼容查询共用 |
303
+ | `NETDISCO_INVENTORY_TIMEOUT` | `300` | 整次清单调用的有限正数秒数 |
304
+ | `NETDISCO_ALLOW_INSECURE_HTTP` | `true` | 显式设为 `false` 拒绝明文 HTTP |
235
305
  | `NET_CONNECTOR_DEVICE_USERNAME`, `NET_CONNECTOR_DEVICE_PASSWORD` | 未设置 | 设备登录默认凭据 |
236
306
  | `NET_CONNECTOR_<VENDOR>_USERNAME`, `NET_CONNECTOR_<VENDOR>_PASSWORD` | 未设置 | 单厂商凭据,例如 `CISCO_IOS` |
237
307
  | `NET_CONNECTOR_BACKUP_DIRECTORY` | `./backups` | 备份及默认报告目录 |
238
308
  | `NET_CONNECTOR_CONCURRENCY` | `4` | 并发设备数,范围 1 至 50 |
309
+ | `NET_CONNECTOR_MAX_SCRIPT_OUTPUT_BYTES` | 未设置 | 每个脚本的累计响应上限;CLI `--max-script-output-bytes N` 优先 |
239
310
  | `NET_CONNECTOR_INCLUDE_HOSTS`, `NET_CONNECTOR_EXCLUDE_HOSTS` | 未设置 | 逗号分隔的管理地址过滤器 |
240
311
  | `NET_CONNECTOR_INCLUDE_VENDORS` | 未设置 | 逗号分隔的厂商标识过滤器 |
241
312
  | `NET_CONNECTOR_VENDOR_OVERRIDES` | `{}` | Netdisco 厂商标签到连接器标识的 JSON 映射 |
@@ -254,7 +325,9 @@ CLI 的计划与批次摘要使用 JSON。非空清单且全部成功时退出
254
325
  export NET_CONNECTOR_DEVICE_RULES='[{"vendor":"Cisco","model_prefix":"N9K","connector":"cisco_nxos"}]'
255
326
  ```
256
327
 
257
- `Settings` 创建客户端或规则时读取环境变量;每台任务启动时才读取设备凭据,因此新批次可接收轮换后的凭据。也可给 `Fleet.new` 注入 `credentials:` 解析器。`fleet.devices` 可在不连接设备时检查映射,随后用 `device.connector(...)` 创建连接器。
328
+ `Fleet` 每次规划或执行前通过 `Settings#snapshot(mode:)` 固定非敏感设置,包括筛选规则、目录、并发、协议、主机密钥、日志、TFTP 参数和清单预算。执行中修改 ENV 不改变当批策略;再次调用会读取新值。CLI 一次调用的规划与执行共用策略,优先级为 CLI > ENV > YAML > 默认值。`Settings#validate!(mode:)` 复用连接配置及 Planner 的枚举和范围规则;`--show-config` 会拒绝非法设置,`--export` 只验证离线目录,不要求清单地址或认证。
329
+
330
+ 每台任务开始时仍读取设备凭据,`Settings.from_file` 也保留轮换能力。纯策略快照不保存密码、API key 或凭据解析器。需要让多次 API 调用共用策略时,可传 `Settings.new.for_run(mode: :backup)`。自定义 `credentials:` 解析器仍可逐设备返回连接设置,它显式给出的选项优先于批次默认值,由调用方负责一致性;注入的 `client:` 生命周期也由调用方管理。传入 `plan:` 的执行不会重新拉取或筛选已批准清单。`fleet.devices` 可在不连接设备时检查映射。
258
331
 
259
332
  ## 开发与验证
260
333
 
@@ -265,6 +338,8 @@ script/ci
265
338
 
266
339
  CI 在 Linux 和 macOS 上覆盖 Ruby 3.2、3.3、3.4、4.0。`script/ci` 扫描源码与可用 Git 历史中的敏感数据,执行 Ruby 与工作流 lint、完整测试,并验证已构建 gem 的隔离安装和本地 PTY 烟测;不连接真实网络设备。首次运行需下载固定校验和的 Gitleaks 与 actionlint。
267
340
 
268
- `bundle exec rake test` 报告已加载库文件的行、分支覆盖率和未加载文件数;当前只记录基线,不作为发布门槛。`bundle exec rake lint` 检查 Ruby 代码,`bundle exec rake security:check` 扫描敏感数据,`bundle exec rake release:check` 执行完整预检。构建产物和脱敏扫描报告保存在被忽略的 `tmp/` 下。实现注释、公开文档和贡献模板均使用中文。
341
+ `bundle exec rake test` 报告当前测试进程的行、分支覆盖率和未加载文件清单。核心引擎、脱敏与错误处理(含 `Redactor`)、批量工作线程分别要求行和分支覆盖率均达到 80%,关键文件没有覆盖率数据也会失败;该门槛同时阻止 CI 和发布预检通过。`bundle exec rake lint` 检查 Ruby 代码,并对 `engine/`、`netdisco/` 限制方法长度(40)和 ABC 复杂度(60)。`bundle exec rake security:check` 扫描敏感数据,`bundle exec rake release:check` 执行完整预检。构建产物和脱敏扫描报告保存在被忽略的 `tmp/` 下。
342
+
343
+ 参与开发见 [CONTRIBUTING.md](CONTRIBUTING.md),漏洞报告见 [SECURITY.md](SECURITY.md)。实现注释和主要文档使用中文,欢迎中文或英文的问题与 PR。
269
344
 
270
345
  真实凭据应放在环境变量和版本库外的本地配置中。检查范围、依赖政策和忽略规则见[验证文档](docs/VERIFICATION.md),发布流程见[发布文档](docs/RELEASING.md)。
data/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # 安全问题上报
2
+
3
+ 请通过 GitHub 的[私密漏洞报告入口](https://github.com/gatework/net-connector/security/advisories/new)联系维护者。仓库已启用 Private vulnerability reporting;报告内容在协作披露前不会作为公开 Issue 发布。普通功能问题和使用疑问请提交 Issue。
4
+
5
+ 报告中请提供受影响的 gem / Ruby / 操作系统版本、涉及的厂商与固件、最小复现步骤、预期和实际结果,以及可能影响的凭据、文件或设备操作。使用文档地址和虚构凭据重现;不要附上真实密码、令牌、私钥、完整生产配置或未经脱敏的会话日志。维护者会在私密报告中沟通复现、修复和披露安排。
6
+
7
+ 凭据泄露、脱敏绕过、命令注入、主机密钥校验绕过、路径越界,以及中断后遗留设备操作等问题均适合私密报告。若发现已公开的真实凭据,请立即在对应系统撤销或轮换,并在私密报告中提供泄露位置;删除当前文件不能撤销已经分发的秘密。
8
+
9
+ 修复优先面向最新发布版本。请尽可能确认问题是否仍存在于最新版本,并注明首次发现问题的版本;旧版本是否回补在具体报告中评估,不承诺每个历史版本都有维护分支。
10
+
11
+ Please [report vulnerabilities privately](https://github.com/gatework/net-connector/security/advisories/new). Include affected versions and a minimal reproduction with synthetic credentials. Reports in English or Chinese are welcome.
data/docs/VERIFICATION.md CHANGED
@@ -15,16 +15,118 @@ script/ci
15
15
  | 步骤 | 内容 |
16
16
  | --- | --- |
17
17
  | `security:check` | 扫描待提交源码和可用的完整 Git 历史;拒绝混入源码的本地凭据、配置和产物 |
18
- | `lint` | 对库、脚本、示例、测试、Gemfile、gemspec 和 Rakefile 执行 RuboCop |
18
+ | `lint` | 对库、脚本、示例、测试、Gemfile、gemspec 和 Rakefile 执行 RuboCop;引擎及 Netdisco 方法上限为 40 行、ABC 60 |
19
19
  | `lint:workflows` | 用 actionlint 校验 GitHub Actions 工作流 |
20
- | `test` | 执行全部 Minitest,并报告已加载库文件的行与分支覆盖率;敏感信息和发布测试使用临时文件、临时仓库与模拟远端响应 |
20
+ | `test` | 执行全部 Minitest,列出未加载文件,并执行关键模块的行与分支覆盖率门槛;敏感信息和发布测试使用临时文件、临时仓库与模拟远端响应 |
21
+ | `benchmark:smoke` | 每项 16 KiB 的合成工作负载,验证假传输、本地 PTY、配置清理、解析、结果保留与日志;只检查正确性,不按机器速度设门槛 |
21
22
  | `package:verify` | 构建 gem,检查元数据、文件白名单、源文件字节和执行位,扫描解包内容及元数据,再进行隔离安装 |
22
23
 
23
24
  隔离安装清除当前 Bundler 和 Ruby 注入变量,分别验证普通 `gem install`
24
25
  和只有 `net-connector` 依赖的最小 Bundler 应用。烟测加载全部厂商,使用本地
25
26
  PTY 子进程采集配置,读取包内 TextFSM 模板,并检查 CLI。它不连接网络设备,
26
27
  也不证明现场设备协议或真实发布服务已验收。
27
- 覆盖率目前只用于观察,不设硬性门槛;未加载文件会单独计数。
28
+
29
+ ## 配置输出隐私回归
30
+
31
+ `test/output_sensitive_test.rb` 使用每次运行新生成、不同于登录凭据的假秘密,
32
+ 覆盖 text/info、text/debug、raw 和外部 logger;检查成功采集、控制符分片、
33
+ 超时、输出超限、设备错误、厂商追加查询、命令替换、结果选择、清理异常和
34
+ 日志器故障。断言包含错误 message/output、底层 message/backtrace、
35
+ `exception.full_message` 及 cause,同时核对完整结果、备份字节与 SHA-256。
36
+ 还验证普通命令诊断恢复、失败重连、非局部退出及元数据复制。
37
+
38
+ `test/transport_test.rb` 另用本地 Ruby PTY 验证配置日志隔离、控制字符、
39
+ 真实读取超时和子进程回收。厂商覆盖来自合成输出,固件范围为 unknown;
40
+ 这些结果不构成真实设备、真实凭据或生产 TFTP 服务验收。
41
+
42
+ ```sh
43
+ bundle exec ruby -Ilib:test test/output_sensitive_test.rb
44
+ bundle exec ruby -Ilib:test test/redaction_contract_test.rb
45
+ bundle exec ruby -Ilib:test test/transport_test.rb
46
+ bundle exec ruby -Ilib:test test/collection_contract_test.rb
47
+ bundle exec ruby -Ilib:test test/running_config_strategy_test.rb
48
+ bundle exec ruby -Ilib:test test/module_loading_test.rb
49
+ ```
50
+
51
+ 实施记录保存在源码树的 `docs/optimization/`,不进入 gem 发布白名单。
52
+ 每个工作包的初始状态、已运行命令与未验证边界均以该记录中的日期和环境为限。
53
+
54
+ `test/module_loading_test.rb` 在独立进程中分别检查设备入口与厂商策略先加载、
55
+ 公共 API 先加载两种顺序,确认业务流程可用、公共层无厂商别名、没有额外厂商
56
+ 或 TextFSM 被提前加载。`test/running_config_strategy_test.rb` 检查同次采集的策略
57
+ 状态贯穿响应校验、步骤选择和清理,覆盖子类 `super`、其他 Fiber 的离线清理、
58
+ 嵌套采集拒绝及失败重连。两者共同约束模块边界和执行行为。
59
+
60
+ ## 日志与命名边界
61
+
62
+ `test/logging_test.rb` 检查安全事件对象、文本与 JSON formatter、会话/命令关联、脚本来源、响应字节和耗时;覆盖 logger 级别在运行中变化、重连分配新标识、回调失败、逐行输出的命令归属,以及调用方 logger 的资源所有权。自定义字段不能覆盖上下文,复杂对象和非有限浮点数不会进入 JSON,敏感范围内的任意事件名称和载荷整体隐藏。
63
+
64
+ 库、示例、测试和隔离安装烟测统一使用 `log_event`、`execute_command` / `execute_script`、`running_config`、单一 `tftp_backup` 回执和 Report。策略接口在 Profile 构造时校验,不通过旧方法名或可选旧钩子回退。历史版本说明和 `docs/optimization/` 的原始记录保留当时名称,不是当前可调用接口。
65
+
66
+ ## 离线内存基准与累计预算
67
+
68
+ `bundle exec ruby script/benchmark_memory.rb --suite baseline --directory tmp/benchmarks/my-run`
69
+ 逐个启动独立 Ruby 进程,报告 1/8/32 MiB 响应、1/10/100 命令、1/4/16 并发的
70
+ 选定组合,不运行最大值的笛卡尔积。单个样本计划响应正文不得超过 128 MiB,
71
+ 已有输出目录拒绝覆盖。较小的 `--suite smoke` 纳入 `rake ci`。
72
+
73
+ 每个样本保留机器信息、实际依赖版本、源码摘要、外部 `/usr/bin/time` 原始统计,
74
+ 以及各阶段的分配对象数、单调耗时、`ps` 测得的 RSS 端点。峰值是 time 的进程
75
+ 高水位,不是 worker/PTY 子进程 RSS 总和;构造夹具和连接在阶段计时前完成,
76
+ 仍计入进程峰值。GC 正常启用。默认日志采用 Configuration 默认值,另有显式
77
+ debug Logger 计数目标样本;不关闭协议检查、丢弃步骤或共享有状态 Parser。
78
+
79
+ 响应构造、终端渲染、配置清理、解析、完整采集与步骤保留分别计量。
80
+ `Result#output` 单次与重复调用单列,不把对象分配数误当字节数,也不据一次采样
81
+ 承诺内存降低比例。带 `--script-budget-bytes N` 的 retain 样本同时验证预算失败
82
+ 及保留输出;它执行的命令较少,不能与完整脚本称为相同工作量的性能提升。
83
+ 基准脚本和原始实施记录不进入运行 gem。
84
+
85
+ `test/script_output_budget_test.rb` 覆盖默认关闭、精确边界、多字节、分页的原始字节、
86
+ 各类钩子追加查询、提示符回调、异常被吞掉后仍失败、故障位置、完整步骤、新脚本
87
+ 重置、原单响应限制及 TFTP 回执。输出隐私矩阵保留日志和原始结果双向断言,
88
+ 真实 PTY 测试核对关闭和 waitpid 回收;设置测试验证优先级、批内不变及离线导出。
89
+
90
+ ## 覆盖率与复杂度门槛
91
+
92
+ `script/coverage.rb` 在测试加载源码前启动 Ruby `Coverage`,由
93
+ `script/coverage_report.rb` 检查以下三个组。各组分别按实际可执行行、分支
94
+ 累计命中比例,不取文件百分比的平均值,不先四舍五入再判断。
95
+
96
+ | 组 | 文件范围 | 行 / 分支最低覆盖率 |
97
+ | --- | --- | --- |
98
+ | 核心引擎 | `lib/net/connector/engine/**/*.rb` | 80% / 80% |
99
+ | 脱敏与错误处理 | `lib/net/connector/engine/errors.rb`,包含 `Redactor` | 80% / 80% |
100
+ | 批量并发 | `lib/net/connector/netdisco/worker.rb` | 80% / 80% |
101
+
102
+ 关键组为空、任一关键文件未测量或组内没有可执行行时均失败。没有分支的组
103
+ 不需要分支命中,但仍须通过行覆盖率及文件加载检查。
104
+ 全库输出中的“未加载文件”表示当前测试进程没有采集到数据:可能只在隔离
105
+ 子进程中加载,也可能在覆盖率启动前被 Bundler 加载,不等同于从未测试。
106
+ 报告会逐项列出路径,不自动预加载源码,也不把子进程覆盖率并入主进程。
107
+
108
+ 每次完整测试同时写入 `tmp/coverage/summary.json`:Ruby 描述和平台、实际依赖版本、
109
+ 库文件总数/已加载数、逐文件及上述三个组的行/分支计数、未命中位置和未加载文件。
110
+ `NET_CONNECTOR_COVERAGE_OUTPUT` 只改变输出位置,不改变门槛。报告不保存配置正文、
111
+ 环境变量值或源码片段。CI 每个 Ruby/平台和最低依赖任务分别上传报告。
112
+
113
+ `script/coverage-baseline.json` 来自 NC-00 实测的初始 dirty 工作树,保存三个组及
114
+ 已加载库总计的原始分数。Ruby 描述(含平台)相同时按整数交叉相乘比较,任一
115
+ 行/分支比例下降均失败;不会自动更新基线。其他运行时标为 `not_comparable`,
116
+ 继续执行原有 80% 门槛与全部行为测试,不能把跨 Ruby 插桩差异当业务回归。
117
+ NC-00 未保存逐文件数据,因此本轮不伪造逐文件历史下限;现在的逐文件报告供后续
118
+ 同环境审阅建立更细基线。基线更新必须审阅,不能用新的低值覆盖旧值来消除失败。
119
+
120
+ 门槛失败由 `Minitest.after_run` 返回非零退出码,`rake test`、`ci`、
121
+ `release:check` 都会失败;测试本身的失败也不会被达标的覆盖率覆盖。
122
+ 单文件开发检查请用 `bundle exec ruby -Ilib -Itest test/<名称>_test.rb`,
123
+ 完整预检仍须运行全部测试,不提供环境变量来降低发布门槛。
124
+
125
+ `.rubocop.yml` 对 `engine/` 和 `netdisco/` 启用 `Metrics/MethodLength`
126
+ (40)及 `Metrics/AbcSize`(60)。这是初始上限;按职责拆分超限方法,
127
+ 不通过自动生成文件豁免维持基线。方法行数按 RuboCop 口径计算。
128
+
129
+ ## 平台与工具
28
130
 
29
131
  CI 矩阵为 Ubuntu 24.04 / macOS 15 × Ruby 3.2、3.3、3.4、4.0。
30
132
  GitHub Actions 固定提交 SHA;Gitleaks 与 actionlint 固定版本和各平台归档
@@ -35,16 +137,47 @@ SHA-256,首次使用时从官方 GitHub Release 下载,缓存到 `tmp/tools/
35
137
 
36
138
  ## 依赖与打包
37
139
 
140
+ `test/topology_stages_test.rb` 使用 `test/support/topology_fixture.rb` 的四类合成对话,验证视图转换、先读回后保存、各阶段超时、读回不匹配/不完整、审批与实际查询一致、旧计划拒绝、保存完成证据和重连不重放。Queue 固定读回完成至保存之前的竞争窗口,检查线程和 Fiber 所有权。PAN-OS 无候选隔离证据时在 I/O 前拒绝自动改写,保留只读解析。夹具的官方来源、推断和 unknown 固件范围见 `test/fixtures/topology/README.md`;测试不等于现场设备认证。普通及最小 Bundler 隔离安装另用本地 PTY 验证分阶段拓扑流程及读回输出敏感标记。
141
+
142
+ `test/tftp_boundary_test.rb` 验证内置策略在首次 I/O 前拒绝不支持的参数组合,使用 Queue 固定 H3C 源探测后的竞争窗口,检查线程/Fiber 拒绝。上传确认后的日志、命令清理、租约清理、路径和元数据钩子失败保留回执且不重传;随机假秘密不进入新错误正文。统一回执测试核对配置来源、实际路径、设备报告等级和冻结字段;Fleet 拒绝第三方和子类提供的完成事实。既有成功/失败/echo/控制符证据用同一套合成夹具继续测试,来源和 unknown 固件范围见 `test/fixtures/tftp/README.md`。普通及最小 Bundler 安装通过本地 PTY 验证唯一的 TFTP 回执入口,没有连接真实 TFTP 服务器或上传配置。
143
+
144
+ `test/netdisco_reporting_test.rb` 覆盖 strict/selected 的状态决策矩阵、显式 CLI 退出码、统一 schema 2、Outcome 的耗时与诊断成员、Fleet/ResultStore 的 Report 契约,以及部分成功/回调/报告错误阻止 selected 成功。动态假秘密放入异常消息、输出、命令、source、line、phase、code 和自定义类型名,报告与私有 JSON 不得含它。受控文件/TFTP 回执阶段跨 Worker 复制仍保留,普通文件异常不能冒充设备产物。可控时钟让 UTC 倒退而单调时间前进,断言设备及批次耗时准确且无负值;没有用固定睡眠推断时间行为。
145
+
146
+ `test/backup_lock_test.rb` 通过 Queue、独立 Ruby 进程及可控单调时钟检查采集前互斥、有限等待、锁释放、线程/Fiber/回调递归、路径别名、私有权限、硬链接/FIFO 拒绝和符号链接替换。`test/backup_identity_test.rb` 验证规范地址命名、改名后的稳定身份、mtime、非普通文件拒绝和非规范文件不参与比较;稳定锁文件不作为多余备份计数。
147
+
148
+ `test/module_loading_test.rb` 在独立 Ruby 进程核验 Netdisco 加载、CLI 离线导出和实际解析的 `$LOADED_FEATURES`;当前设备与厂商入口的双加载顺序分别检查,确认公共层无厂商别名,Storage 不加载设备。`test/parsing_encoding_test.rb` 覆盖 UTF-8/二进制标签、非法 UTF-8、Latin-1 字节、回车/退格/ANSI、被控制符隐藏的非法输入、分裂多字节字符、原备份不变及拓扑不能把异常输出当作空表。原有 PAN-OS 引号多行拒绝及多线程独立解析测试保留。没有真实其他编码设备样本,本次不增加自动或显式设备编码配置;严格转码能力留待实际设备需求验证。
149
+
150
+ `test/file_persistence_test.rb` 对临时文件创建、写入、flush/fsync、rename、父目录打开/同步及收尾注入故障,检查磁盘内容、阶段回执、无正文诊断、Fleet 部分成功及报告/导出行为。同步不支持与 EIO 分开,第三方回执子类不能供应完成事实。隔离安装烟测还通过真实本地 PTY 采集写入,验证私有锁、目录同步及安全读取。本地 macOS 测试证明协议与故障分支;Linux、网络文件系统、真实断电恢复需单独验证。
151
+
152
+ `test/netdisco_budget_test.rb` 用合成响应、可控单调时钟及本地 TCP HTTP 端点验证单页/累计字节、记录数、分页、认证和兼容查询的共用期限。流式上限覆盖 chunked、无长度和虚报长度;超限时设备工厂与凭据解析器均未调用。阻塞读取场景断言关闭自有传输并回收观察线程,不靠固定 sleep 判断竞态。注入 requester 只验证返回后的预算,不声称能强制中止任意回调。
153
+
154
+ `test/netdisco_settings_test.rb` 验证批内 ENV 策略变化被隔离、逐设备凭据轮换、后续批次刷新、批准计划不重抓清单、CLI/ENV/YAML 优先级及离线导出。策略快照的序列化和 inspect 不含秘密;非法枚举、采样范围及预算在创建 Fleet 或设备前拒绝。默认预算没有经过生产容量压测,真实 Netdisco 及多平台远端矩阵仍需单独验收。
155
+
38
156
  运行依赖写在 `net-connector.gemspec`,包括直接使用的、可能从 Ruby 默认
39
157
  安装中拆出的标准库 gem。开发工具只写在 Gemfile,不进入运行依赖。
40
- `expect-pty` 使用 `~> 0.3.1`;开发用 `parallel` 保持 1.x,以支持 Ruby 3.2。
158
+ 共享脱敏要求 `expect-pty ~> 0.5.0`,使用已发布的公共 `Expect::Redactor`。
159
+ 开发及隔离安装检查使用正式依赖包,不再需要本地 expect 补丁或联调环境包装脚本。
160
+ 开发用 `parallel` 保持 1.x,以支持 Ruby 3.2。
41
161
 
42
162
  本项目是库,`Gemfile.lock` 仅作本地开发记录并被忽略;各 Ruby 版本的 CI
43
163
  分别解析兼容依赖。应用使用者应在自己的应用中提交 lockfile。测试和打包
44
164
  脚本通过隔离安装检查运行依赖,避免依赖开发环境里偶然存在的 gem。
45
165
 
46
- gem 只收录库代码、TextFSM 模板、CLI、架构/验证/发布文档、README、LICENSE 和
47
- CHANGELOG。测试、示例、发布工具、工作流、本地评审快照、配置和备份不进入包。
166
+ `gemfiles/minimum.gemfile` 固定关键业务依赖下限 expect-pty 0.5.0、textfsm 0.2.0;
167
+ 标准库和开发工具仍按主 Gemfile 的兼容范围解析。普通矩阵检查允许版本的正常解析,
168
+ 另在 Ubuntu / Ruby 3.2、4.0 检查下限组合的全量测试与隔离安装。本地复现:
169
+
170
+ ```sh
171
+ BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle install
172
+ BUNDLE_GEMFILE=gemfiles/minimum.gemfile bundle exec rake test package:verify
173
+ ```
174
+
175
+ 这不是所有传递依赖最旧版本的笛卡尔积,也不声称本地 macOS 运行证明远端矩阵通过。
176
+ 当前这两个关键依赖的正常解析与下限恰好相同,报告仍记录实际版本,供以后比较。
177
+ 开发锁文件均忽略;gemspec 保持兼容范围,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
178
+
179
+ gem 只收录库代码、TextFSM 模板、CLI、架构/验证/发布文档、README、LICENSE、
180
+ CHANGELOG、SECURITY 和 CONTRIBUTING。测试、示例、发布工具、工作流、本地评审快照、配置和备份不进入包。
48
181
  更改 gemspec 后,实际归档仍须通过独立的文件白名单检查。
49
182
 
50
183
  ## 敏感数据