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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/README.md +92 -20
- data/docs/VERIFICATION.md +17 -7
- data/docs/architecture.md +102 -64
- data/examples/netdisco_database.yml +16 -0
- data/lib/net/connector/device/base.rb +27 -107
- data/lib/net/connector/device/local_backup.rb +96 -0
- data/lib/net/connector/device/profile.rb +8 -3
- data/lib/net/connector/device/running_config/strategy.rb +1 -1
- data/lib/net/connector/device/running_config.rb +44 -5
- data/lib/net/connector/device/save_config.rb +21 -0
- data/lib/net/connector/device/tftp/file_upload.rb +53 -0
- data/lib/net/connector/{operations/tftp_receipt.rb → device/tftp/receipt.rb} +18 -20
- data/lib/net/connector/device/tftp/strategy.rb +67 -0
- data/lib/net/connector/device/tftp/target.rb +63 -0
- data/lib/net/connector/device/tftp.rb +117 -0
- data/lib/net/connector/device/topology/immediate_strategy.rb +40 -0
- data/lib/net/connector/device/topology/interface_description.rb +46 -0
- data/lib/net/connector/device/topology/interface_name.rb +58 -0
- data/lib/net/connector/device/topology/strategy.rb +75 -0
- data/lib/net/connector/device/topology.rb +277 -0
- data/lib/net/connector/engine/authentication.rb +2 -2
- data/lib/net/connector/engine/configuration.rb +1 -1
- data/lib/net/connector/engine/dialogue.rb +21 -21
- data/lib/net/connector/engine/error_metadata.rb +50 -0
- data/lib/net/connector/engine/errors.rb +15 -1
- data/lib/net/connector/engine/execution.rb +17 -10
- data/lib/net/connector/engine/log/event.rb +66 -0
- data/lib/net/connector/engine/log/formatter.rb +16 -0
- data/lib/net/connector/engine/log/messages.rb +52 -0
- data/lib/net/connector/engine/log/stream.rb +56 -0
- data/lib/net/connector/engine/log.rb +105 -84
- data/lib/net/connector/engine/session.rb +83 -42
- data/lib/net/connector/engine/terminal_text.rb +33 -0
- data/lib/net/connector/engine/transport.rb +2 -2
- data/lib/net/connector/netdisco/batch.rb +9 -45
- data/lib/net/connector/netdisco/cli.rb +17 -10
- data/lib/net/connector/netdisco/client.rb +4 -3
- data/lib/net/connector/netdisco/config_file.rb +6 -3
- data/lib/net/connector/netdisco/database_client.rb +197 -0
- data/lib/net/connector/netdisco/device.rb +4 -5
- data/lib/net/connector/netdisco/diagnostic.rb +9 -39
- data/lib/net/connector/netdisco/fleet.rb +26 -39
- data/lib/net/connector/netdisco/report.rb +37 -16
- data/lib/net/connector/netdisco/result_store.rb +2 -2
- data/lib/net/connector/netdisco/settings.rb +26 -9
- data/lib/net/connector/netdisco.rb +1 -0
- data/lib/net/connector/{operations → storage}/backup_lock.rb +2 -2
- data/lib/net/connector/{operations → storage}/private_file.rb +2 -7
- data/lib/net/connector/{operations → storage}/safe_file.rb +1 -1
- data/lib/net/connector/{operations → storage}/saved_config.rb +10 -35
- data/lib/net/connector/storage.rb +15 -0
- data/lib/net/connector/textfsm.rb +72 -0
- data/lib/net/connector/vendor/cisco_ios/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/cisco_ios/topology.rb +5 -5
- data/lib/net/connector/vendor/cisco_nxos/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/h3c/tftp_backup.rb +4 -4
- data/lib/net/connector/vendor/h3c/topology.rb +5 -5
- data/lib/net/connector/vendor/h3c.rb +3 -3
- data/lib/net/connector/vendor/hillstone/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/hillstone/topology.rb +5 -5
- data/lib/net/connector/vendor/huawei/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/palo_alto/running_config.rb +1 -1
- data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/palo_alto/topology.rb +3 -6
- data/lib/net/connector/vendor/radware/tftp_backup.rb +3 -3
- data/lib/net/connector/vendor/radware/topology.rb +2 -2
- data/lib/net/connector/version.rb +1 -1
- data/lib/net/connector.rb +1 -6
- metadata +47 -39
- data/lib/net/connector/device/interface_description.rb +0 -44
- data/lib/net/connector/device/interface_name.rb +0 -56
- data/lib/net/connector/engine/base.rb +0 -4
- data/lib/net/connector/engine/log_messages.rb +0 -47
- data/lib/net/connector/engine/profile.rb +0 -4
- data/lib/net/connector/engine.rb +0 -4
- data/lib/net/connector/operations/local_backup.rb +0 -89
- data/lib/net/connector/operations/parse_output.rb +0 -77
- data/lib/net/connector/operations/running_config/cisco.rb +0 -14
- data/lib/net/connector/operations/running_config/hillstone.rb +0 -12
- data/lib/net/connector/operations/running_config/palo_alto.rb +0 -12
- data/lib/net/connector/operations/running_config/strategy.rb +0 -3
- data/lib/net/connector/operations/running_config.rb +0 -16
- data/lib/net/connector/operations/saved_config/legacy_index.rb +0 -109
- data/lib/net/connector/operations/tftp/cisco_ios.rb +0 -11
- data/lib/net/connector/operations/tftp/cisco_nxos.rb +0 -11
- data/lib/net/connector/operations/tftp/file_upload.rb +0 -55
- data/lib/net/connector/operations/tftp/h3c.rb +0 -11
- data/lib/net/connector/operations/tftp/hillstone.rb +0 -11
- data/lib/net/connector/operations/tftp/huawei.rb +0 -11
- data/lib/net/connector/operations/tftp/palo_alto.rb +0 -11
- data/lib/net/connector/operations/tftp/radware.rb +0 -11
- data/lib/net/connector/operations/tftp/strategy.rb +0 -75
- data/lib/net/connector/operations/tftp_backup.rb +0 -183
- data/lib/net/connector/operations/topology/cisco.rb +0 -11
- data/lib/net/connector/operations/topology/h3c.rb +0 -11
- data/lib/net/connector/operations/topology/hillstone.rb +0 -11
- data/lib/net/connector/operations/topology/immediate_strategy.rb +0 -45
- data/lib/net/connector/operations/topology/palo_alto.rb +0 -11
- data/lib/net/connector/operations/topology/radware.rb +0 -11
- data/lib/net/connector/operations/topology/strategy.rb +0 -87
- data/lib/net/connector/operations/topology.rb +0 -261
- data/lib/net/connector/operations.rb +0 -26
data/docs/architecture.md
CHANGED
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
# 设备操作架构
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个连接器对应一台设备的一段会话。`Base` 组合设备能力,提供统一的命令执行和会话入口;各项能力的公开方法、流程和策略放在同一功能目录。公共层不反向依赖厂商,也不保留另一套 Operations 入口。
|
|
4
4
|
|
|
5
5
|
| 层次 | 职责 | 位置 |
|
|
6
6
|
| --- | --- | --- |
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| 清单计划 | 保存不可变快照,校验任务槽位、跳过原因和目标冲突 | `netdisco/plan.rb` |
|
|
23
|
-
| 批量执行 | 分派独立设备任务,隔离回调故障 | `netdisco/worker.rb` |
|
|
24
|
-
| 批量结果 | 汇总设备状态并保留报告故障 | `netdisco/batch.rb` |
|
|
25
|
-
| 设备集合 | 读取清单、执行本地或 TFTP 任务、写报告 | `netdisco/fleet.rb` |
|
|
26
|
-
| 设置 | 读取环境变量及 YAML 覆盖项 | `netdisco/settings.rb` |
|
|
7
|
+
| 会话引擎 | 登录、传输、命令、脚本及资源所有权 | `engine/` |
|
|
8
|
+
| 日志 | 事件上下文与生命周期;安全事件、格式和字节流分别实现 | `engine/log.rb`、`engine/log/` |
|
|
9
|
+
| 设备入口 | 能力组合、会话代理、通用脚本钩子 | `device/base.rb` |
|
|
10
|
+
| 设备档案 | 不可变规则与策略接口校验;Builder 负责声明 DSL | `device/profile.rb`、`device/profile/` |
|
|
11
|
+
| 配置采集与保存 | 采集策略生命周期、完整性校验、显式保存 | `device/running_config.rb`、`device/running_config/`、`device/save_config.rb` |
|
|
12
|
+
| 本地备份 | 采集、摘要比较、文件持久化和完成回执 | `device/local_backup.rb` |
|
|
13
|
+
| TFTP | 参数预检、上传、完成证据、目标和回执 | `device/tftp.rb`、`device/tftp/` |
|
|
14
|
+
| 拓扑 | 邻居发现、接口名称/描述、变更计划与分阶段执行 | `device/topology.rb`、`device/topology/` |
|
|
15
|
+
| 厂商差异 | 档案组装及各项能力的厂商策略 | `vendor/<厂商>.rb`、`vendor/<厂商>/` |
|
|
16
|
+
| 文件存储 | 路径锁、安全读取、私有原子写入、离线配置 | `storage.rb`、`storage/` |
|
|
17
|
+
| 解析 | TextFSM 模板选择、记录转换及异常映射 | `textfsm.rb`、`templates/` |
|
|
18
|
+
| 终端文本 | 严格 UTF-8 字节验证和终端渲染 | `engine/terminal_text.rb`、`engine/terminal_renderer.rb` |
|
|
19
|
+
| 清单与批量编排 | 清单预算、策略快照、计划校验、工作线程和报告 | `netdisco/` |
|
|
20
|
+
|
|
21
|
+
`Base` 引入 `RunningConfig::Capability`、`LocalBackup::Capability`、`Tftp::Capability`、`Topology::Capability`、`TextFSM::Capability` 及 `SaveConfig`。能力入口与实现共置,Base 不再重复业务包装。配置采集的临时策略绑定也由 RunningConfig 管理;备份与拓扑通过 `device.running_config` 复用同一流程。
|
|
27
22
|
|
|
28
23
|
## 公开业务方法与确认范围
|
|
29
24
|
|
|
@@ -33,18 +28,17 @@
|
|
|
33
28
|
|
|
34
29
|
| 入口 | 正常返回 | 失败和前置约束 |
|
|
35
30
|
| --- | --- | --- |
|
|
36
|
-
| `
|
|
37
|
-
| `running_config`
|
|
31
|
+
| `execute_command`、`execute_script` | `Result`,`steps` 保留完整已完成响应 | 参数/脚本构造错误可直接抛出;执行、钩子和预算错误通常进入 Result,不自动重放 |
|
|
32
|
+
| `running_config` | `Result`,`config` 为通过完整性检查的清理副本 | 执行或清理失败保留步骤;自定义档案/策略构造错误仍可能直接抛出 |
|
|
38
33
|
| `save_config` | `Result` | 不支持时为失败 Result;该入口确认命令响应,不提供拓扑读回或设备介质验证 |
|
|
39
34
|
| `backup(path:)` | `Backup`,含 path、bytes、sha256、collected_at、change、previous_sha256 | 采集/文件错误直接抛出;已 rename 后的持久性错误携带完成回执,不能视为未写入 |
|
|
40
|
-
| `tftp_backup` |
|
|
41
|
-
| `tftp_backup_receipt` | 包含原 transfer 的 `TftpReceipt` | 与旧入口相同;当前执行路径只报告 `device_reported`,无服务端摘要 |
|
|
35
|
+
| `tftp_backup` | `TftpReceipt`,直接包含 server、path、时间、来源、格式及核验等级 | 参数先校验;失败/未确认直接抛出。完成后故障为 `TftpCompletionError`,保留回执 |
|
|
42
36
|
| `parse_command`、`parse_config` | TextFSM 记录数组 | 命令失败、模板错误、严格 UTF-8/解析错误直接抛出,不用空数组代替未知输出 |
|
|
43
37
|
| `neighbors`、`interface_descriptions` | `Neighbor` 数组、接口到描述的 Hash | 不支持、未知/部分输出或读取失败直接抛出;明确空表才允许空结果 |
|
|
44
38
|
| `plan_interface_descriptions` | 冻结的 `Topology::Plan` | 只读证据和能力检查;PAN-OS 当前拒绝自动改写计划,无隐式 commit |
|
|
45
39
|
| `apply_interface_descriptions(plan, confirmed:)` | `Result`,包括变更后已完成的读回/保存步骤 | 未确认、错误设备/旧命令序列、能力不足及重验失败在写入前直接抛出;`stale_plan` 是 `DeviceError`。开始变更后的领域失败保留步骤 |
|
|
46
|
-
| `SavedConfig#read/#parse/#export` | 原始字节、记录数组;export 返回目标路径或 stdout 模式的 nil |
|
|
47
|
-
| `Fleet#backup_all/#tftp_backup_all` |
|
|
40
|
+
| `SavedConfig#read/#parse/#export` | 原始字节、记录数组;export 返回目标路径或 stdout 模式的 nil | 纯本地,只读取规范地址文件;缺失、不安全文件、解析或写入错误直接抛出 |
|
|
41
|
+
| `Fleet#backup_all/#tftp_backup_all` | 统一 `Report`,内部包含原始执行快照 `Batch` | 设置、外部计划和清单错误在派发前抛出;设备/关闭/回调/报告普通故障保留在批次中,线程中断仍清理后传播 |
|
|
48
42
|
|
|
49
43
|
确认分为三个独立层次:
|
|
50
44
|
|
|
@@ -62,21 +56,21 @@
|
|
|
62
56
|
|
|
63
57
|
`LocalBackup` 在采集前取得 `BackupLock`,持有到摘要比较、替换与结果构造结束。锁名由 realpath 父目录与归一化文件名决定;Unicode NFC、大小写折叠后的摘要同时覆盖尚未创建的目标。区分大小写的文件系统也保守合并这些锁,目标名称本身不变。锁文件用 NOFOLLOW/0600 打开,再验证普通文件、所有者、单硬链接和 inode;释放仅关闭 FD,不删除锁文件。默认 `flock(LOCK_EX | LOCK_NB)`,可选等待共用有限单调期限,超时均为 `BackupBusy`。
|
|
64
58
|
|
|
65
|
-
Fleet
|
|
59
|
+
Fleet 在规范目标校验、凭据解析及连接器构造之前加同一把锁,再向下层备份授权一次同进程、同 Fiber 的借用。借用在采集前消费,回调递归备份不能重复使用;没有全局路径缓存。路径锁在外、会话锁在内,`Base#backup` 拒绝已有会话操作中的嵌套调用。独立进程必须采用同一 flock 协议;调用方保护目录及祖先,锁不约束不合作的写入者。
|
|
66
60
|
|
|
67
|
-
`SafeFile` 对一次 NOFOLLOW/NONBLOCK 打开的 FD 做 fstat 和读取,拒绝非普通文件。`SavedConfig#read/#fingerprint`、离线导出和解析复用该边界;`find`
|
|
61
|
+
`SafeFile` 对一次 NOFOLLOW/NONBLOCK 打开的 FD 做 fstat 和读取,拒绝非普通文件。`SavedConfig#read/#fingerprint`、离线导出和解析复用该边界;`find` 返回当时已验证的路径,不保证调用方日后重新打开时身份不变。直接备份继续安全替换末级符号链接,Fleet/SavedConfig 则保留拒绝契约。内容不变时再核对目录项身份,避免因路径替换跳过必要写入;正常未变文件保持 mtime。
|
|
68
62
|
|
|
69
|
-
`PrivateFile.write`
|
|
63
|
+
`Storage::PrivateFile.write` 统一返回 `Receipt`,执行临时文件写入、flush/fsync、rename、父目录 fsync,并跟踪 not_committed/committed/durable 和出错阶段。rename 前失败保留旧文件;之后失败保留新文件。目录 fsync 的 EINVAL/ENOSYS/ENOTSUP/EOPNOTSUPP/NotImplementedError 单独表示不支持;其他错误不能降级为成功。durable 只表示同步调用成功,未变内容不补验过去写入,断电恢复也未在本地测试中验证。
|
|
70
64
|
|
|
71
|
-
提交后的 `BackupPersistenceError` 只携带原 `Backup` 元数据和受控写入回执,Fleet 保留产物并使用已有 saved_with_error 分类。只接受库定义的具体完成错误及匹配产物类型,第三方异常的 backup 属性或自定义 WriteError 子类不作为完成证据。报告保留已提交位置,导出错误说明 committed
|
|
65
|
+
提交后的 `BackupPersistenceError` 只携带原 `Backup` 元数据和受控写入回执,Fleet 保留产物并使用已有 saved_with_error 分类。只接受库定义的具体完成错误及匹配产物类型,第三方异常的 backup 属性或自定义 WriteError 子类不作为完成证据。报告保留已提交位置,导出错误说明 committed;设备命令不会自动重放。
|
|
72
66
|
|
|
73
67
|
TFTP 只确认设备报告的上传结果:策略去掉命令、应答和提示符回显后,检查明确的完成行;文件名或表示“即将上传”的进度文字不算成功。原始输出或终端渲染文本中的失败证据优先于成功文字。
|
|
74
68
|
|
|
75
69
|
TFTP 内置策略的 `validate_options!` 是纯参数校验,执行于 H3C 源文件探测之前。整个探测、脚本、证据核对、回执和事件记录由原有 `with_operation(:tftp_backup)` 独占;其他线程、Fiber 及回调重入均不能插入设备命令。租约只保护当前会话,不能隔离其他设备连接或服务器上的同名文件。
|
|
76
70
|
|
|
77
|
-
`tftp_backup`
|
|
71
|
+
`tftp_backup` 只返回不可变 `TftpReceipt`,直接包含来源、格式、请求路径和实际 `path`、完成时间及核验等级。只有设备完成证据时为 device_reported,没有服务器 SHA-256。H3C 自动源探测为 startup,显式文件为 saved_file/unknown;山石为 startup/dat,Cisco 为 running/cfg,PAN-OS 为 running/xml,Radware 为 native_archive/tgz。公共策略的默认来源为 unknown,不能据扩展名或字节数推断服务器内容。
|
|
78
72
|
|
|
79
|
-
明确完成证据先生成最小回执,再执行路径提取、元数据钩子及事件记录。已完成步骤之后的清理错误同样保留回执;路径不可用时保留 nil,不把请求文件名冒充实际目标。`TftpCompletionError` 只含受控码、类型和回执,不带原始消息、输出或 cause。Fleet 仅信任库定义的精确错误类及匹配产物;第三方异常上的
|
|
73
|
+
明确完成证据先生成最小回执,再执行路径提取、元数据钩子及事件记录。已完成步骤之后的清理错误同样保留回执;路径不可用时保留 nil,不把请求文件名冒充实际目标。`TftpCompletionError` 只含受控码、类型和回执,不带原始消息、输出或 cause。Fleet 仅信任库定义的精确错误类及匹配产物;第三方异常上的 receipt 字段和错误子类不能供应完成事实。已上传但收尾失败使用 reported_with_error/partial 分类。
|
|
80
74
|
|
|
81
75
|
`Topology` 读取邻居和旧描述,冻结计划,要求显式确认,并在写入前重验全部证据和重建的命令。`ImmediateStrategy` 将即时生效设备的修改/退出视图、运行配置读回、保存拆开:读回未确认或有未识别接口块时停止,不发送保存。重验、下发、读回、保存及阶段间隙都持有原有会话租约;只有所属线程和 Fiber 能顺序执行脚本,其他调用、关闭请求和脚本回调重入均返回 `SessionBusy`。租约不隔离其他设备会话或管理员。
|
|
82
76
|
|
|
@@ -102,21 +96,27 @@ PAN-OS 使用候选配置模型。[官方提交说明](https://docs.paloaltonetw
|
|
|
102
96
|
|
|
103
97
|
`Worker` 按线程完成顺序接收终止通知,设备结果仍写入原清单槽位。任一线程中断时,调用方无需等待先创建的慢线程;创建后续线程失败时,也会停止并等待已启动的任务清理资源。普通设备故障继续转换为逐台结果,回调故障单独记录。
|
|
104
98
|
|
|
105
|
-
设备耗时由 CLOCK_MONOTONIC 度量,包含 on_start、设备任务及关闭,截止于 on_result
|
|
99
|
+
设备耗时由 CLOCK_MONOTONIC 度量,包含 on_start、设备任务及关闭,截止于 on_result 前。报告的总耗时覆盖目录准备和全部 worker/callback,截止于报告写入前;两者不以 UTC 时间相减。Outcome 的 duration_ms 和 Diagnostic 直接声明为 Data 成员,未测量的耗时为 nil。`with` 替换审计时间时清除原计时,替换错误字段时清除原诊断;显式提供的新值仍经过验证。
|
|
106
100
|
|
|
107
|
-
|
|
101
|
+
Fleet 始终返回 Report,委托 Batch 的执行结果及严格 success?/status,单独提供 policy 和 policy_success?。默认 strict 要求非空且全部成功;selected 必须至少包含一个成功结果,其他状态只能为 filtered/sample_limit,且 callback_errors 为空、report_error 为 nil。部分成功、任何其他跳过和未知状态都阻止策略成功。
|
|
108
102
|
|
|
109
|
-
|
|
103
|
+
报告 summary 使用单一 schema_version: 2,包含 policy、policy_success、coverage、单调 duration_ms、逐台 diagnostic 及 report_diagnostic。coverage 仅按已尝试状态计数,失败和部分成功也算尝试。`ResultStore#write(report, directory:)` 始终接收 Report。已存文件是写入前快照;写入自身失败时,返回对象与 CLI 携带最终报告故障。`Batch#build_report` 只构造报告,不重跑任务,也不能补出未记录的总耗时。
|
|
110
104
|
|
|
111
|
-
Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态;不调用异常 inspect/to_h,不保留异常引用、正文、消息、回溯、命令、source 或 line。未知错误码/阶段为 nil,未知类型归为 StandardError。
|
|
105
|
+
Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态;不调用异常 inspect/to_h,不保留异常引用、正文、消息、回溯、命令、source 或 line。未知错误码/阶段为 nil,未知类型归为 StandardError。Report 也筛选手工构造结果中的 error_code/error_type、callback_errors 和 report_error。产物阶段仅来自与实际 Backup/TftpReceipt 匹配的库内精确错误类;通用文件回执只在报告写入边界使用,不能冒充设备备份完成。
|
|
112
106
|
|
|
113
107
|
`Planner` 先按厂商采样,再按实际 TFTP 文件名排除覆盖冲突,保留采样顺序中的首台设备。未入选设备仍标记为 `sample_limit`,只有入选后目标重名才标记为 `remote_filename_collision`。`Plan#validate!` 校验任务与清单的对应关系;调用方传入或通过 `with` 修改的计划也必须在读取凭据、创建目录及设备 I/O 前通过校验。冲突规则适用于全部厂商,包括不同地址规范化后产生相同文件名的情况。
|
|
114
108
|
|
|
115
109
|
`Settings` 保留动态秘密来源,`snapshot(mode:)` 只复制允许公开的非敏感字段,并在副作用前复用 `Configuration`、`Planner` 和 `Client.options` 校验。Fleet 的一次规划/执行只使用该份冻结策略;CLI 通过 `for_run` 将规划与执行绑定到同一策略。每台设备的凭据读取与策略解析分开,自定义凭据解析器给出的显式连接选项仍保留原优先级。带 `plan:` 的执行不再查询清单;以后新建的批次可以读取新策略和新的 API 凭据。
|
|
116
110
|
|
|
117
|
-
`Client#devices` 每次持有独立 `InventoryBudget
|
|
111
|
+
`Client#devices` 每次持有独立 `InventoryBudget`,认证、分页、QueryRequired 查询共同累计正文字节和去重前记录数,并共享单调时钟 deadline。默认 HTTP 使用 `read_body`,先检查实际接收字节再追加,不信任 Content-Length。每阶段缩短原生连接/读/写超时;总期限还覆盖慢速响应头。期限观察线程只关闭本次拥有的 HTTP 连接,防止 chunked 的收尾读取拖延返回,调用结束即唤醒并 join。默认 HTTP 不做隐式 GET 重试;这一机制不进入设备命令路径。
|
|
118
112
|
|
|
119
|
-
|
|
113
|
+
注入的 `requester` 接收 URI 和 request。它返回后才接受长度和期限检查,不在任意用户 Ruby 回调中注入异步异常。解析和集合操作完成后也检查期限,但这不构成任意 CPU 回调的抢占保证。预算失败仅报告错误码与安全类型,没有部分清单、响应正文或底层 cause。HTTPS 的标准证书检查不变;默认允许明文 HTTP,可通过显式策略禁止。
|
|
114
|
+
|
|
115
|
+
`Settings#inventory_source` 选择 HTTP `Client` 或 PostgreSQL `DatabaseClient`,两者只向 Fleet 提供 `devices`,不改变设备映射、计划和执行接口。数据库 SQL 与标量参数由用户配置,不内置业务表名或筛选;结果用列别名适配 `Client::FIELDS`,只保留这份现有字段白名单。连接参数由 `DatabaseClient::CONNECTION_ENV` 集中映射到 Netdisco 的 `NETDISCO_DB_*` 环境变量,全部排除在纯策略快照外;每次新建清单客户端时读取最新值。
|
|
116
|
+
|
|
117
|
+
`DatabaseClient` 每次调用独占 PostgreSQL 连接,在只读事务中用扩展查询协议声明游标,原生解析器拒绝多语句和非查询输入。FETCH 大小及次数复用清单预算,单行模式使 libpq 不缓存整页;每行在追加前计数,额外列也消耗字节预算。驱动必须先解码单行,所以单个超大字段仍可超过 Ruby 预算的瞬时内存。总 deadline 覆盖连接和所有语句,每次 FETCH 前缩短 statement_timeout;正常完成回滚只读事务,异常关闭连接,任何失败均不交付部分清单。NOTICE、原始数据库异常及 cause 不进入日志或 CLI;稳定错误码区分连接失败、查询失败、无效清单和预算超限。SQL/参数可在 show-config 中查看,不能用于传递凭据。
|
|
118
|
+
|
|
119
|
+
数据库集成测试运行 `bundle exec rake test:postgres`,通过 `pg_config --bindir`(或 `NET_CONNECTOR_TEST_PG_BINDIR`)定位服务端工具。测试只创建临时 SCRAM 数据库和私有 Unix socket,退出时停止并删除;不会读取真实 Netdisco 凭据或连接已有服务。覆盖 SQL/参数、只读限制、真实认证失败、查询中途失败、各项预算、连接回收以及 CLI 计划。
|
|
120
120
|
|
|
121
121
|
## 迁移与尚未启用的能力
|
|
122
122
|
|
|
@@ -128,7 +128,7 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
|
|
|
128
128
|
| 文件锁及持久性 | 备份前获取稳定的私有锁文件,调用方不要按目录文件总数推断备份数或在运行中删除锁文件;已提交但收尾失败仍有产物,不自动覆盖重试 |
|
|
129
129
|
| 清单和脚本预算 | 清单预算默认有界;max_script_output_bytes 默认为 nil、显式启用。两者都不是整批设备执行的硬 deadline,累计脚本超限不表示命令未执行 |
|
|
130
130
|
| HTTP | HTTPS 校验保持;默认允许既有明文 HTTP,部署可显式禁止。设置快照不冻结逐设备动态凭据解析 |
|
|
131
|
-
| 报告和成功策略 |
|
|
131
|
+
| 报告和成功策略 | 统一 Report/schema 2;默认 strict,selected 只忽略预期过滤/采样跳过。设备部分成功、回调/报告错误仍阻止策略成功 |
|
|
132
132
|
| 解析 | 只在解析副本上严格检查 UTF-8;非法数据直接报错。原始备份/导出不转码,其他设备编码等待真实样本再扩展 |
|
|
133
133
|
|
|
134
134
|
任务书 NC-12(协作取消和整批 deadline)为后置可选项,本轮 **deferred**:没有新增
|
|
@@ -162,13 +162,13 @@ NC-07C(TFTP 服务端验证适配器)同为可选扩展,本轮 **deferred*
|
|
|
162
162
|
|
|
163
163
|
提示符和交互标记是 Ruby 正则表达式,应短到能放入未匹配尾部;流式适配器不支持需要无限长历史的模式。收集输出的上限与匹配窗口上限不同。终端渲染器只处理常见行编辑控制符,不是完整屏幕终端模拟器。`Profile#terminal_size` 使用 `[宽, 高]`,PTY 适配器转换为 Ruby 的 `[行, 列]`。
|
|
164
164
|
|
|
165
|
-
`max_script_output_bytes` 为正 Integer 或 nil。计数属于每个 Execution,主命令以及准备、提示符和后处理钩子的 `Execution#
|
|
165
|
+
`max_script_output_bytes` 为正 Integer 或 nil。计数属于每个 Execution,主命令以及准备、提示符和后处理钩子的 `Execution#execute_command` 共用计数,包含原始分页标记和最终提示符,不能用 `capture: false` 绕过。登录和提权认证保留各自原有单响应上限。每次脚本新建计数器;一次租约中的多个脚本、多个设备和多个批次不共用它。
|
|
166
166
|
|
|
167
|
-
累计等于上限时当前脚本可正常结束,但下一条查询在发送前失败;超过上限的完整主响应先进入 steps,再返回 `ScriptOutputLimitExceeded
|
|
167
|
+
累计等于上限时当前脚本可正常结束,但下一条查询在发送前失败;超过上限的完整主响应先进入 steps,再返回 `ScriptOutputLimitExceeded`,后处理和下一命令不会继续。追加查询不进入主脚本的公开 steps,但计入预算并在错误中保留实际查询命令。吞掉查询预算异常的钩子不能让超额脚本报告成功。已执行命令不重放;确认的 TFTP 完成行仍可构造带收尾错误的回执。
|
|
168
168
|
|
|
169
|
-
累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS
|
|
169
|
+
累计检查不在读取中途切断当前命令,最多还会接收一个受 `max_output_bytes` 限制的响应;已完成步骤不截断或丢弃。配置清理、解析、用户回调和反复 `Result#output` 的副本不计入该字节预算,因此它不是 RSS 或整批内存硬上限。合成基准不足以确定适合所有设备的默认阈值,因此默认不限制累计值。设置通过 YAML `ssh.max_script_output_bytes`、`NET_CONNECTOR_MAX_SCRIPT_OUTPUT_BYTES` 和 CLI 同名选项进入批次策略快照,优先级为 CLI > ENV > YAML;未配置时不传递该可选键,显式凭据 resolver 可覆盖连接参数。
|
|
170
170
|
|
|
171
|
-
Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限声明入口,厂商策略负责差异行为。公开方法、厂商钩子、结果对象和 CLI JSON
|
|
171
|
+
Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限声明入口,厂商策略负责差异行为。公开方法、厂商钩子、结果对象和 CLI JSON 字段以当前文档为准;内部不做运行时方法注入,也没有工作流 DSL。
|
|
172
172
|
|
|
173
173
|
## 关键依赖的维护状态与替代方案
|
|
174
174
|
|
|
@@ -192,7 +192,7 @@ Ruby 对象显式拥有资源并使用关键字参数。`Profile` 提供有限
|
|
|
192
192
|
| 依赖 | 已有隔离边界 | 替代方案与验收条件 |
|
|
193
193
|
| --- | --- | --- |
|
|
194
194
|
| `expect-pty` | `Transports::Pty` 包装信道,设备构造支持 `transport:` 注入 | 可维护受控分支,或实现 Ruby `PTY` / `IO.select` 适配器;必须通过有序匹配、分片、写入超时、EOF、中断、子进程回收及日志脱敏测试 |
|
|
195
|
-
| `textfsm` | `
|
|
195
|
+
| `textfsm` | `Net::Connector::TextFSM` 集中构造解析器和映射异常 | 可维护受控分支,或在该入口接入另一解析实现;必须保持索引选择、模板语义、输出字段及错误码,并验证现有和新增模板样本 |
|
|
196
196
|
|
|
197
197
|
传输替换还不是完全即插即用:`Session`、`ResponseReader`、`Authentication`、
|
|
198
198
|
`Execution` 等直接使用 `Expect.monotonic`,配置及命令校验使用
|
|
@@ -250,15 +250,15 @@ router = VariantRouter.new(host: "192.0.2.10", username: "operator")
|
|
|
250
250
|
router.supports?(:tftp_backup) # => true,不连接设备
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
配置采集可把 `running_config_strategy` 绑定到 `Net::Connector::RunningConfig::Strategy`
|
|
253
|
+
配置采集可把 `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 负责通用脚本与锁内回调,不决定配置是否完整。
|
|
254
254
|
|
|
255
|
-
拓扑策略通过 `topology_strategy YourStrategy`
|
|
255
|
+
拓扑策略通过 `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` 提供模板。
|
|
256
256
|
|
|
257
257
|
业务操作针对单个连接器构造,并提供 `call`。TFTP 策略只处理厂商传输差异,公共操作统一成功和失败规则。生成的文件名经过 `TftpTarget` 校验及长度限制;带作用域的 IPv6 地址会转为安全 ASCII 标记,特别长的地址使用稳定 SHA-256 标记。原本合法的文件名保留拼写,只有整体过长才缩短清单名称。TFTP 返回值表示设备报告上传完成,服务器文件核对仍由调用方负责。
|
|
258
258
|
|
|
259
|
-
TFTP 策略的类方法 `filename(host, label: nil)`
|
|
259
|
+
TFTP 策略的类方法 `filename(host, label: nil)` 是必需的无 I/O 命名接口,默认使用 `file_extension` 声明的扩展名。Netdisco 只清理清单名称,不维护厂商扩展名或固定文件名分支。Radware 和山石分别声明 tgz、dat,PAN-OS 返回固定名称。H3C、华为继承公共 `Tftp::FileUpload`,共用上传脚本、默认源文件名和完成证据,各自负责源文件探测与校验。
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`receipt_metadata(target, source_file:, explicit_source:)`、`resolve_source_file`、`default_path`、`remote_path`、`script` 和 `device_reported_complete?`。可继承 `Tftp::Strategy` 的公共默认值;重写上传流程时必须一并审视继承的参数约束和来源声明。预检不得访问设备,元数据只允许 configuration_kind/source_file/format/requested_path。接口缺失在 Profile 构造时失败,不再动态回退到另一套行为。
|
|
262
262
|
|
|
263
263
|
服务器核验适配器尚未接入;TFTP API 不会自动生成 server_verified 结果。PAN-OS 固定名仍按现有计划拒绝同批碰撞,串行运行不改变该限制,也不保证跨进程或跨批次隔离。
|
|
264
264
|
|
|
@@ -280,23 +280,49 @@ TFTP 策略的类方法 `filename(host, label: nil)` 是不产生 I/O 的命名
|
|
|
280
280
|
不能把不确定的执行结果当成“尚未执行”。跨进程租约、断点恢复和持久队列
|
|
281
281
|
属于调用平台的职责,当前 `Worker` 只负责一个进程内的有界并发与资源清理。
|
|
282
282
|
|
|
283
|
-
##
|
|
283
|
+
## 加载入口与厂商策略
|
|
284
|
+
|
|
285
|
+
`require "net/connector"` 加载设备 API 和引擎,厂商通过 autoload 按需加载。TextFSM 能力模块可预先组合,但外部 `textfsm` gem 只在实际解析时加载。仅需会话引擎时使用 `require "net/connector/engine/core"`;离线文件操作使用 `net/connector/storage`,不加载厂商。
|
|
284
286
|
|
|
285
|
-
`
|
|
287
|
+
公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Topology::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
|
|
286
288
|
|
|
287
|
-
`
|
|
289
|
+
`TerminalText.utf8` 在副本上验证设备或文件的原始字节,`TerminalText.render` 再使用严格终端渲染。Ruby 源码默认 UTF-8 不决定 PTY 或 `File.binread` 返回的编码。非法原始字节不能因控制符擦除而通过检查;渲染损坏多字节字符时同样拒绝,错误为不含正文或 cause 的 `invalid_output_encoding`。拓扑在表头匹配和计数前复用验证,TextFSM 每次解析创建独立 Parser。日志允许转义非法字节,原始备份不转码。
|
|
288
290
|
|
|
289
|
-
|
|
291
|
+
### 当前命名与接口调整
|
|
292
|
+
|
|
293
|
+
项目在开发阶段只维护当前接口,不保留旧路径转发或旧方法别名。
|
|
294
|
+
|
|
295
|
+
| 原名称或入口 | 当前入口 |
|
|
296
|
+
| --- | --- |
|
|
297
|
+
| `engine`、`engine/base`、`engine/profile` | `net/connector`;底层分别为 `engine/core`、`device/base`、`device/profile` |
|
|
298
|
+
| `Operations::*`、`operations/` | 设备流程为 `device/`;文件为 `Storage` / `storage/`;解析为 `TextFSM` / `textfsm.rb` |
|
|
299
|
+
| 公共层中的厂商策略别名 | `Net::Connector::<Vendor>::RunningConfig` / `TftpBackup` / `Topology`,位于 `vendor/<厂商>/` |
|
|
300
|
+
| `record_event` | `log_event` |
|
|
301
|
+
| `execute`、引擎 `query` / `exchange` | `execute_command` |
|
|
302
|
+
| `run`、引擎 `execute` 脚本入口 | `execute_script` |
|
|
303
|
+
| `collect_config` | `running_config` |
|
|
304
|
+
| `tftp_backup_receipt`、三字段 `TftpBackup` | `tftp_backup` 返回 `TftpReceipt`;实际目标为 `path`;完成后错误通过 `receipt` 取回 |
|
|
305
|
+
| `PrivateFile.write_receipt` | `Storage::PrivateFile.write` 返回持久化回执 |
|
|
306
|
+
| `report_schema`、`--report-schema` | 删除版本选择;Fleet 始终返回 Report,summary 固定 schema 2 |
|
|
307
|
+
| `Batch#report`、`Device#connector` | `build_report`、`build_connector` |
|
|
308
|
+
| `Settings#credentials_for` | `device_credentials_for`;非敏感设置由批次 Policy 单独提供 |
|
|
309
|
+
| `check_response` | `validate_response!` |
|
|
310
|
+
| 拓扑 `enter_configuration` / `leave_configuration` | `enter_configuration_command` / `leave_configuration_commands` |
|
|
311
|
+
| TFTP `source_file` / `complete?` | `resolve_source_file` / `device_reported_complete?` |
|
|
312
|
+
| `command_scope` / `output_scope` | `with_command_redaction` / `with_sensitive_output` |
|
|
313
|
+
| `LegacyIndex` 和旧名称文件查找 | 只读取规范 `<IP>.txt`,无目录扫描和迁移分支 |
|
|
314
|
+
|
|
315
|
+
工厂方法用 `build_*` 表明返回新对象;抛错校验使用 `validate_*!`;块作用域使用明确的 `with_*`。单命令、脚本、命令文本构造和完成证据判断分别命名,避免从通用的 execute/check/complete 猜测行为。`legacy_ssh` 表示设备 SSH 协议协商选项,Netdisco QueryRequired 则是服务端 API 行为,两者均独立于本库旧版本接口。
|
|
290
316
|
|
|
291
317
|
## 接口描述规则
|
|
292
318
|
|
|
293
|
-
原始 `Neighbor` 字段和计划证据完整保留发现值。`InterfaceName.key` 用于匹配本机接口别名与运行配置名称;`InterfaceName.configuration` 保留配置命令的接口展开规则。`InterfaceName.short` 只决定描述里对端接口的显示形式,绝不替换本机下发命令中的接口名。
|
|
319
|
+
原始 `Neighbor` 字段和计划证据完整保留发现值。`Topology::InterfaceName.key` 用于匹配本机接口别名与运行配置名称;`Topology::InterfaceName.configuration` 保留配置命令的接口展开规则。`Topology::InterfaceName.short` 只决定描述里对端接口的显示形式,绝不替换本机下发命令中的接口名。
|
|
294
320
|
|
|
295
|
-
`InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商默认计划共用的纯函数,生成 `To <名称> <接口>`。已知接口族会缩写,并保留原有大小写:Ethernet/Eth 为 Eth,GigabitEthernet/GE/Gi 为 Gi,Ten-GigabitEthernet/TenGigabitEthernet/XGE/Te 为 Te,FastEthernet/Fa 为 Fa,port-channel/Po 为 Po。端口编号与子接口后缀保留。`ge-0/0/1`、`100GE1/0/1`、`Port 12` 等未知形式默认不变;这是有限映射,不声称识别全部厂商命名。
|
|
321
|
+
`Topology::InterfaceDescription.format(neighbor, abbreviate: true, lowercase: false)` 是各厂商默认计划共用的纯函数,生成 `To <名称> <接口>`。已知接口族会缩写,并保留原有大小写:Ethernet/Eth 为 Eth,GigabitEthernet/GE/Gi 为 Gi,Ten-GigabitEthernet/TenGigabitEthernet/XGE/Te 为 Te,FastEthernet/Fa 为 Fa,port-channel/Po 为 Po。端口编号与子接口后缀保留。`ge-0/0/1`、`100GE1/0/1`、`Port 12` 等未知形式默认不变;这是有限映射,不声称识别全部厂商命名。
|
|
296
322
|
|
|
297
323
|
默认建议因此从 `To peer Ethernet1/2` 变为 `To peer Eth1/2`。`abbreviate: false` 保留原拼写,`lowercase: true` 仅将对端接口改为小写。自定义代码块拿到原始邻居,可生成完整描述。输出仍必须经过 80 字节及字符校验、证据复核、确认和回读。
|
|
298
324
|
|
|
299
|
-
`InterfaceDescription.commands(interface:, description:, leave: "exit")` 为 IOS/NX-OS 和山石分别生成 `interface`、`description`、退出命令;H3C 使用 `leave: "quit"
|
|
325
|
+
`Topology::InterfaceDescription.commands(interface:, description:, leave: "exit")` 为 IOS/NX-OS 和山石分别生成 `interface`、`description`、退出命令;H3C 使用 `leave: "quit"`。进入、退出全局配置视图和保存完成证据由厂商负责。各阶段命令分别由策略声明,PAN-OS 的命令构造/超时辅助方法不会启用未经验证的候选提交。命令构造器本身不连接设备,也不能绕开已审核的拓扑计划。
|
|
300
326
|
|
|
301
327
|
## 敏感命令的生命周期
|
|
302
328
|
|
|
@@ -308,25 +334,37 @@ TFTP 策略的类方法 `filename(host, label: nil)` 是不产生 I/O 的命名
|
|
|
308
334
|
|
|
309
335
|
`RunningConfig` 将每个厂商采集步骤标记为输出敏感,包括 PAN-OS 的前后候选差异查询。`Base#perform_script` 在已持有会话锁、已登录后保护批次准备,在执行完命令后保护结果选择和清理;两处都在离开范围前归一化异常。命令范围覆盖厂商准备、追加查询和回调,即使钩子用新命令替换原命令也继承原有保护。敏感标记不改变锁的 Thread/Fiber 所有权,不开放回调重入,不触发自动重放。
|
|
310
336
|
|
|
311
|
-
这改变了配置采集的日志默认值:text/debug/raw 文件及注入的 logger 不再得到配置正文,没有关闭该保护的采集开关。显式的 `
|
|
337
|
+
这改变了配置采集的日志默认值:text/debug/raw 文件及注入的 logger 不再得到配置正文,没有关闭该保护的采集开关。显式的 `execute_command`/`Script` 保持原默认值,调用方编写读取配置的命令时应指定 `output_sensitive: true`;命令文本含秘密时仍须同时使用 `sensitive: true`。普通脚本下一步及下一次操作恢复各自的诊断规则。配置正文不会加入长期脱敏词表,也不会为了日志而清洗 `Result.steps`、`Result.config` 或备份字节。
|
|
312
338
|
|
|
313
339
|
保护覆盖连接器管理的日志与异常边界。调用方自行将 `result.output`、`value!` 或钩子收到的原始响应写入其他系统,仍是在显式处理敏感业务数据。脱敏不表示设备命令未执行;失败结果中的已完成步骤必须保留,不能据此自动重试命令。
|
|
314
340
|
|
|
315
|
-
##
|
|
341
|
+
## 本地备份标识
|
|
342
|
+
|
|
343
|
+
批量备份只用规范化管理地址命名为 `<IP>.txt`,IPv6 的 `:` 改为 `_`。清单名称保留在结果元数据及 TFTP 文件名中。`Storage::SavedConfig` 只读取该规范路径,不扫描其他名称;缺失时 required 查找或读取抛出 ENOENT,`find(required: false)` 返回 nil。
|
|
344
|
+
|
|
345
|
+
新建规范文件时报告 created,之后只与同一路径的内容比较。设备改名不会改变身份或基线,其他名称文件不会被读取、迁移或覆盖。Fleet 在凭据解析和连接前检查规范目标,拒绝符号链接和非普通文件;持有路径锁直至备份结束。`SafeFile` 在同一 FD 上完成检查和读取;它与 flock 均不能阻止不合作的外部写入者,目录保护仍由调用方负责。
|
|
346
|
+
|
|
347
|
+
## 日志组件与事件
|
|
348
|
+
|
|
349
|
+
`Log` 管理日志资源、级别和上下文;`Log::Event` 冻结已脱敏的标量字段;`Log::Formatter` 提供标准 Logger 文本格式;`Log::Transcript` 以行组织设备回显,`Log::RedactingWriter` 将分片直接交给 Expect::Redactor。日志模块不再实现另一套秘密匹配算法。
|
|
350
|
+
|
|
351
|
+
连接、命令和操作失败统一经过 `Log#log_failure`。错误类型、错误码和阶段由 `ErrorMetadata` 的固定词表过滤,Netdisco 报告复用同一规则;未知错误码不输出,未知阶段使用引擎当前阶段,未知类型归为 StandardError。异常正文仍先由会话脱敏,防止配置钩子把未登记的配置放进错误元数据后进入日志。
|
|
352
|
+
|
|
353
|
+
操作后处理无论抛出异常还是返回失败 Result,都在敏感作用域退出前归一化错误,再记录完成事件;Result 的已完成步骤及配置内容保持原样。最终处理继承实际执行的命令及交互敏感性,包括准备钩子、替换命令和追加查询;只累计标记,不长期保留秘密,也不影响后续普通命令的日志。
|
|
316
354
|
|
|
317
|
-
|
|
355
|
+
错误归一化通过 `Error#with_diagnostics` 保留内置错误的业务回执,并新建异常的原生状态,避免复制原 cause 或调用栈。自定义错误子类使用脱敏后的参数重新构造,不能复制可能参与消息呈现的原始私有字段。会话租约退出时若日志收尾失败,已返回的 Result 仍保留步骤、配置和原业务错误;仅在原操作成功时附加日志错误。
|
|
318
356
|
|
|
319
|
-
|
|
357
|
+
每次连接有独立 session_id,每次发送命令分配 command_id;operation、phase、source、line 贯穿命令开始、回显、完成和错误。command_complete 在 info 级别记录 duration_ms、response_bytes,status: response_received 只表示响应已完成。operation_complete 在脚本准备、执行、回调及最终处理之后记录成功或失败,不代替文件持久性或 TFTP 服务端核验。
|
|
320
358
|
|
|
321
|
-
|
|
359
|
+
`device.log_event(name, level: :info, **fields)` 是唯一自定义事件入口。会话身份和命令上下文由引擎提供,不能从 fields 覆盖。事件名和字段值经终端渲染与脱敏后冻结;非法字段名丢弃,复杂对象和非有限浮点数隐藏,不调用任意对象的 inspect/to_s。敏感范围内自定义事件整体隐藏,包括事件名和任意字段,防止配置钩子输出未登记的秘密。
|
|
322
360
|
|
|
323
|
-
|
|
361
|
+
注入的 logger 由调用方持有;连接器不修改级别、formatter、progname,也不关闭它。每次写入同时检查配置与 logger 当前级别。消息对象支持 to_s/inspect 和安全 to_h,应用可自行输出 JSON。自有文本日志使用毫秒时间及逐行事件;raw 文件只写经过敏感保护的字节。上下文退出前先收尾渲染与脱敏缓冲,避免上一命令尾部被标成下一条命令。
|
|
324
362
|
|
|
325
363
|
## 公开契约与 Rails 接入
|
|
326
364
|
|
|
327
|
-
应用代码优先使用 `Net::Connector.open/build`、`Base` 的公开设备方法、`Command`、`Script`、结果对象,以及 `Netdisco::Fleet`。厂商扩展使用 `Profile` DSL 和文档中列出的策略接口;`Profile::Builder`、`Session`
|
|
365
|
+
应用代码优先使用 `Net::Connector.open/build`、`Base` 的公开设备方法、`Command`、`Script`、结果对象,以及 `Netdisco::Fleet`。厂商扩展使用 `Profile` DSL 和文档中列出的策略接口;`Profile::Builder`、`Session` 的状态字段、工作线程调度及解析辅助方法属于内部实现。扩展直接加载当前设备模块或厂商目录下的类。项目仍处于 0.x,公开契约的变更会在更新记录中说明。
|
|
328
366
|
|
|
329
|
-
gem 不依赖 Rails,也不自动创建数据库模型或管理应用的连接池。`logger: Rails.logger`
|
|
367
|
+
gem 不依赖 Rails,也不自动创建数据库模型或管理应用的连接池。`logger: Rails.logger` 由应用持有,连接器写入安全事件对象,不修改或关闭它。数据库报告通过 `ResultStore::Database` 注入仓储,写报告发生在调用线程中;逐设备凭据解析、连接器工厂和回调则运行在工作线程中。
|
|
330
368
|
|
|
331
369
|
在 Rails 内调用涉及模型、自动加载或应用状态的线程代码时,应由应用用 `Rails.application.executor.wrap` 包住相应调用。仅在外层包住 `fleet.backup_all` 不会覆盖子线程。例如,凭据解析器可以写为:
|
|
332
370
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 数据库连接只从 NETDISCO_DB_* 环境变量读取;此文件可直接编辑并纳入版本控制。
|
|
2
|
+
netdisco:
|
|
3
|
+
source: postgres
|
|
4
|
+
query: |
|
|
5
|
+
SELECT host(ip) AS ip, name, dns, vendor, os, model, os_ver, serial
|
|
6
|
+
FROM device
|
|
7
|
+
WHERE vendor = $1
|
|
8
|
+
ORDER BY ip
|
|
9
|
+
query_params: [H3C]
|
|
10
|
+
page_size: 500
|
|
11
|
+
max_devices: 100000
|
|
12
|
+
inventory_timeout: 300
|
|
13
|
+
|
|
14
|
+
backup:
|
|
15
|
+
directory: ./backups
|
|
16
|
+
concurrency: 4
|
|
@@ -5,14 +5,23 @@ require "forwardable"
|
|
|
5
5
|
require_relative "../engine/core"
|
|
6
6
|
require_relative "profile"
|
|
7
7
|
require_relative "running_config"
|
|
8
|
-
require_relative "
|
|
9
|
-
require_relative "
|
|
8
|
+
require_relative "save_config"
|
|
9
|
+
require_relative "local_backup"
|
|
10
|
+
require_relative "tftp"
|
|
11
|
+
require_relative "topology"
|
|
12
|
+
require_relative "../textfsm"
|
|
10
13
|
|
|
11
14
|
module Net
|
|
12
15
|
module Connector
|
|
13
16
|
# 设备连接门面;厂商子类提供语法和钩子,组合对象负责实际执行。
|
|
14
17
|
class Base
|
|
15
18
|
extend Forwardable
|
|
19
|
+
include RunningConfig::Capability
|
|
20
|
+
include SaveConfig
|
|
21
|
+
include LocalBackup::Capability
|
|
22
|
+
include Tftp::Capability
|
|
23
|
+
include Topology::Capability
|
|
24
|
+
include TextFSM::Capability
|
|
16
25
|
|
|
17
26
|
attr_reader :configuration, :command_timeout
|
|
18
27
|
|
|
@@ -121,7 +130,7 @@ module Net
|
|
|
121
130
|
def close = @session.close
|
|
122
131
|
|
|
123
132
|
# 将一条文本命令包装成脚本并执行。
|
|
124
|
-
def
|
|
133
|
+
def execute_command(text, **, &)
|
|
125
134
|
execute_script(Script.new([Command.new(text, **)]), &)
|
|
126
135
|
end
|
|
127
136
|
|
|
@@ -131,66 +140,9 @@ module Net
|
|
|
131
140
|
perform_script(script, &)
|
|
132
141
|
end
|
|
133
142
|
|
|
134
|
-
# 使用简短名称执行脚本。
|
|
135
|
-
alias run execute_script
|
|
136
|
-
|
|
137
|
-
# 读取运行配置并返回清理后的配置结果。
|
|
138
|
-
def running_config
|
|
139
|
-
RunningConfig.new(self).call
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# 执行命令并按厂商、命令或显式 TextFSM 模板返回结构化记录。
|
|
143
|
-
def parse_command(command, template: nil, template_dir: nil)
|
|
144
|
-
parser = Operations::ParseOutput.new(template_dir: template_dir)
|
|
145
|
-
parser.call(execute(command).value!, template: template, vendor: vendor, command: command, host: host)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# 采集运行配置并使用指定 TextFSM 模板提取结构化记录。
|
|
149
|
-
def parse_config(template:, template_dir: nil)
|
|
150
|
-
parser = Operations::ParseOutput.new(template_dir: template_dir)
|
|
151
|
-
parser.call(running_config.value!, template: template, host: host)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# 执行 CDP 或 LLDP 查询并返回统一的链路邻居记录。
|
|
155
|
-
def neighbors = Operations::Topology.new(self).neighbors
|
|
156
|
-
|
|
157
|
-
# 读取运行配置中的接口描述或端口名称。
|
|
158
|
-
def interface_descriptions = Operations::Topology.new(self).descriptions
|
|
159
|
-
|
|
160
|
-
# 以邻居和现有配置为证据,生成待确认的接口描述变更计划。
|
|
161
|
-
def plan_interface_descriptions(abbreviate: true, lowercase: false, &formatter)
|
|
162
|
-
Operations::Topology.new(self).plan_descriptions(abbreviate: abbreviate, lowercase: lowercase, &formatter)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# 明确确认且现场证据未变化时执行接口描述计划。
|
|
166
|
-
def apply_interface_descriptions(plan, confirmed: false)
|
|
167
|
-
Operations::Topology.new(self).apply(plan, confirmed: confirmed)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
143
|
# 多步骤业务操作独占当前会话,内部脚本仍禁止回调重入。
|
|
171
144
|
def with_operation(name, &block) = @session.with_operation(name, &block)
|
|
172
145
|
|
|
173
|
-
# 采集配置并以原子方式保存为私有文件。
|
|
174
|
-
# 采集失败时保留已有备份文件。
|
|
175
|
-
def backup(path:, lock_timeout: 0)
|
|
176
|
-
@session.assert_path_lock_order!(:backup)
|
|
177
|
-
Operations::LocalBackup.new(self).call(path: path, lock_timeout: lock_timeout)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# 要求设备直接向 TFTP 服务器导出原生配置。
|
|
181
|
-
# 完成仅表示设备报告传输成功,未读取服务器端文件。
|
|
182
|
-
def tftp_backup(host:, path: nil, source_file: nil, vrf: nil)
|
|
183
|
-
Operations::TftpBackup.new(self).call(host: host, path: path, source_file: source_file, vrf: vrf)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# 明确请求来源、格式及设备报告等级;旧入口继续返回原有三字段对象。
|
|
187
|
-
def tftp_backup_receipt(host:, path: nil, source_file: nil, vrf: nil)
|
|
188
|
-
Operations::TftpBackup.new(self).call_receipt(host: host, path: path, source_file: source_file, vrf: vrf)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# 配置采集是设备的基础能力,两个公共入口共享同一流程。
|
|
192
|
-
def collect_config = RunningConfig.new(self).call
|
|
193
|
-
|
|
194
146
|
# 业务层可扩展脚本准备、响应校验和最终结果,所有钩子均在会话锁内执行。
|
|
195
147
|
def execute_operation(script, name:, prompt: nil, after_command: nil, privilege: true, &finalize)
|
|
196
148
|
perform_script(script, operation: name, prompt: prompt, after_command: after_command,
|
|
@@ -201,20 +153,10 @@ module Net
|
|
|
201
153
|
def current_prompt = @session.prompt
|
|
202
154
|
|
|
203
155
|
# 将业务事件写入当前设备会话日志。
|
|
204
|
-
def
|
|
156
|
+
def log_event(name, **details)
|
|
205
157
|
@session.log_event(name, **details)
|
|
206
158
|
end
|
|
207
159
|
|
|
208
|
-
# 执行厂商保存配置命令;不支持时返回显式失败结果。
|
|
209
|
-
def save_config
|
|
210
|
-
if save_commands.empty?
|
|
211
|
-
return Result.new(error: @session.error(UnsupportedOperation, "saving configuration is not supported",
|
|
212
|
-
phase: :save))
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
execute_script(save_commands)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
160
|
# 进入特权模式,并把当前提示符记录到会话。
|
|
219
161
|
def enable
|
|
220
162
|
@session.perform(:enable) { @session.enable(enable_command, enable_prompt) }
|
|
@@ -226,28 +168,11 @@ module Net
|
|
|
226
168
|
@session.interact(input: input, output: output, escape: escape, timeout: timeout)
|
|
227
169
|
end
|
|
228
170
|
|
|
229
|
-
# 返回读取运行配置所需的设备命令;厂商必须实现。
|
|
230
|
-
def config_commands
|
|
231
|
-
commands = profile.config_commands
|
|
232
|
-
return commands if commands
|
|
233
|
-
|
|
234
|
-
raise NotImplementedError, "#{self.class} must define running configuration commands"
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# 返回保存配置命令;空数组表示设备不支持保存。
|
|
238
|
-
def save_commands = profile.save_commands
|
|
239
|
-
|
|
240
|
-
# 清理运行配置文本;厂商可移除设备回显噪声。
|
|
241
|
-
def clean_config(text) = config_strategy.clean(text)
|
|
242
|
-
|
|
243
171
|
# 返回不包含凭据的连接状态摘要。
|
|
244
172
|
def inspect = "#<#{self.class} host=#{host.inspect} state=#{state}>"
|
|
245
173
|
|
|
246
174
|
protected
|
|
247
175
|
|
|
248
|
-
# 默认取最后一个已完成步骤;厂商可选择配置所在的业务步骤。
|
|
249
|
-
def config_result_step(result) = config_strategy.result_step(result)
|
|
250
|
-
|
|
251
176
|
# 匹配设备分页提示,供对话层自动发送翻页响应。
|
|
252
177
|
def pager_pattern = profile.pager_pattern
|
|
253
178
|
|
|
@@ -321,22 +246,6 @@ module Net
|
|
|
321
246
|
|
|
322
247
|
private
|
|
323
248
|
|
|
324
|
-
# 采集器仅在持有会话锁的结果处理阶段绑定策略,让旧方法钩子的 super
|
|
325
|
-
# 复用响应校验状态。其他 Fiber 的离线清理仍使用自己的临时策略。
|
|
326
|
-
def with_config_strategy(strategy)
|
|
327
|
-
previous = @config_strategy_scope
|
|
328
|
-
@config_strategy_scope = [Fiber.current, strategy]
|
|
329
|
-
yield
|
|
330
|
-
ensure
|
|
331
|
-
@config_strategy_scope = previous
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
# 只允许当前 Fiber 复用采集中的策略;离线调用创建独立策略。
|
|
335
|
-
def config_strategy
|
|
336
|
-
scope = @config_strategy_scope
|
|
337
|
-
scope && scope.first.equal?(Fiber.current) ? scope.last : RunningConfig.strategy(self)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
249
|
# 将厂商提示、失败模式和对话钩子组装成不可变对话语法。
|
|
341
250
|
def build_dialogue
|
|
342
251
|
Dialogue.new(
|
|
@@ -364,9 +273,20 @@ module Net
|
|
|
364
273
|
execution.context[:privilege] = privilege
|
|
365
274
|
output_sensitive = script.any?(&:output_sensitive?)
|
|
366
275
|
@session.perform(:script) do
|
|
367
|
-
@session.
|
|
368
|
-
|
|
369
|
-
|
|
276
|
+
@session.log_script(operation: operation, steps: execution.steps) do
|
|
277
|
+
@session.with_sensitive_output(output_sensitive) { before_batch(execution) }
|
|
278
|
+
result = execution.execute_script(script, &on_step)
|
|
279
|
+
private_result = output_sensitive || script.any?(&:sensitive?) || execution.sensitive?
|
|
280
|
+
@session.with_sensitive_output(private_result) do
|
|
281
|
+
result = finalize ? finalize.call(result) : result
|
|
282
|
+
# 回调也可直接返回失败;与抛错共用脱敏边界,保留已完成步骤及业务配置。
|
|
283
|
+
if result.is_a?(Result) && result.failure?
|
|
284
|
+
result = Result.new(steps: result.steps, config: result.config,
|
|
285
|
+
error: @session.normalize_error(result.error, phase: :script))
|
|
286
|
+
end
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
end
|
|
370
290
|
end
|
|
371
291
|
rescue Error => error
|
|
372
292
|
Result.new(steps: execution ? execution.steps : [], error: error)
|