net-connector 0.13.0 → 0.14.1
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 +19 -0
- data/README.md +1 -1
- data/README.zh-CN.md +1 -1
- data/docs/VERIFICATION.md +14 -5
- data/docs/architecture.md +9 -4
- data/docs/netdisco.md +3 -3
- data/docs/netdisco.zh-CN.md +7 -7
- data/docs/usage.md +2 -2
- data/docs/usage.zh-CN.md +2 -2
- data/examples/backup.yml +47 -8
- data/examples/inventory.yml +39 -0
- data/lib/net/connector/device/base.rb +3 -3
- data/lib/net/connector/device/running_config.rb +1 -1
- data/lib/net/connector/device/scope.rb +5 -1
- data/lib/net/connector/device/tftp/strategy.rb +8 -3
- data/lib/net/connector/device/tftp.rb +13 -4
- data/lib/net/connector/device/topology/audit.rb +103 -0
- data/lib/net/connector/device/topology/plan.rb +4 -4
- data/lib/net/connector/device/topology.rb +23 -1
- data/lib/net/connector/engine/error/metadata.rb +10 -2
- data/lib/net/connector/engine/errors.rb +4 -2
- data/lib/net/connector/engine/known_hosts.rb +40 -21
- data/lib/net/connector/netdisco/address_filter.rb +86 -0
- data/lib/net/connector/netdisco/cli/backup.rb +80 -25
- data/lib/net/connector/netdisco/cli/help.rb +47 -0
- data/lib/net/connector/netdisco/cli/options.rb +111 -18
- data/lib/net/connector/netdisco/cli/topology.rb +358 -0
- data/lib/net/connector/netdisco/config/initializer.rb +146 -0
- data/lib/net/connector/netdisco/{config_file.rb → config.rb} +26 -4
- data/lib/net/connector/netdisco/device/policy.rb +77 -0
- data/lib/net/connector/netdisco/device.rb +14 -3
- data/lib/net/connector/netdisco/fleet/options.rb +93 -0
- data/lib/net/connector/netdisco/fleet.rb +114 -59
- data/lib/net/connector/netdisco/inventory/cache.rb +42 -4
- data/lib/net/connector/netdisco/inventory/http.rb +35 -21
- data/lib/net/connector/netdisco/operations/batch.rb +1 -1
- data/lib/net/connector/netdisco/operations/outcome.rb +2 -2
- data/lib/net/connector/netdisco/operations.rb +8 -8
- data/lib/net/connector/netdisco/plan.rb +39 -16
- data/lib/net/connector/netdisco/planner.rb +22 -8
- data/lib/net/connector/netdisco/progress.rb +45 -2
- data/lib/net/connector/netdisco/rules.rb +34 -17
- data/lib/net/connector/netdisco/settings.rb +88 -23
- data/lib/net/connector/netdisco/tftp/policy.rb +152 -0
- data/lib/net/connector/netdisco/topology/options.rb +48 -0
- data/lib/net/connector/netdisco/worker.rb +2 -1
- data/lib/net/connector/netdisco.rb +1 -0
- data/lib/net/connector/vendor/h3c/tftp.rb +30 -0
- data/lib/net/connector/vendor/radware/tftp.rb +10 -1
- data/lib/net/connector/version.rb +1 -1
- metadata +60 -49
- data/examples/inventory_sql.yml +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1feb1332558f1cad30b6bdb1b657c16cf3237d5ba975014204b320a94e0a84c
|
|
4
|
+
data.tar.gz: b7294d094cd32498f7c6e8539ecb40fbf227f9b4926772f93ccd63696b728b30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c4700d752016b6b448c5f302791b7ad2cb87904024fc267c81aaa0cec2d2398799bae6f0662fa70719fcdf5958b49919c1a6e0f40cca8ad78f35cdb37a94947
|
|
7
|
+
data.tar.gz: 972e0acaa88aa59bf1e2d5b7447976c2fbceeb18e360b905c228b0919488e6c1facbf0f2491a141010650da281f0206770f257ce27c7774fac4c08a054cd1c6c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.14.1 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
- HTTP 清单在每次调用内复用认证和分页的连接及期限观察线程,保留总预算、逐块限额和禁用隐式请求重试的规则。
|
|
6
|
+
- known_hosts 合并以条目索引避免重复扫描,并复用原子写入和父目录同步;持久化未确认及不安全文件使用明确错误码。
|
|
7
|
+
- 修复设备作用域中同步关闭故障被延迟取消覆盖、callable 结果回调被隐式数组转换忽略,以及错误构造冻结调用方输出缓冲的问题。
|
|
8
|
+
- 必需提示符或抽象设备命令缺失时抛出 `ConfigurationError`,普通批次故障隔离可继续处理其他设备。
|
|
9
|
+
- 诊断复制校验内置错误的类身份,防止自定义异常伪装类名后保留未脱敏的私有字段。
|
|
10
|
+
|
|
11
|
+
## 0.14.0 - 2026-10-18
|
|
12
|
+
|
|
13
|
+
- 不兼容调整:Netdisco 配置与策略入口整理为 `Config`、`Config::Initializer`、`Device::Policy`、`Topology::Options`,原常量和加载路径已移除,直接引用者需更新。
|
|
14
|
+
- 示例入口统一为 `backup.rb`、`backup_tftp.rb` 和 `topology.rb`,单台 TFTP 使用 `backup_tftp.rb --host IP`,数据库示例配置改为 `inventory.yml`,原 `device_tftp.rb`、`review_tftp.rb` 和 `inventory_sql.yml` 已移除。
|
|
15
|
+
- 备份示例支持默认配置、首次运行初始化、常用与完整帮助及清单预览,直接使用当前安装或 Bundler 选定的库。
|
|
16
|
+
- 新增拓扑发现、描述审计和只读规划入口,只有显式 `--apply` 才下发,应用前重验邻居及原描述,修改后回读并保存,部分失败和中断保留已完成结果。
|
|
17
|
+
- 清单支持 CIDR、地址范围、名称与厂商筛选、排除和重复参数,新增只读缓存模式,离线预览不连接设备或创建批次目录。
|
|
18
|
+
- TFTP 支持逐设备服务器、源接口、源文件与 VRF 设置,核验目录按服务器绑定,修复仅配置服务器目录映射时将同服务器 PAN-OS 设备误判为文件名冲突的问题。
|
|
19
|
+
- 示例的公开配置按 CLI、YAML、环境默认值依次取值,宿主可通过 `load_env: true` 启用相同规则,正式 CLI 保留原有优先级。
|
|
20
|
+
- 运行时依赖解除版本约束,由宿主应用选择并锁定兼容版本。
|
|
21
|
+
|
|
3
22
|
## 0.13.0 - 2026-10-18
|
|
4
23
|
|
|
5
24
|
- 不兼容调整:H3C 的 LLDP 列序变体合并为 `h3c/display_lldp_neighbor_information_list.textfsm`,显式引用旧模板路径的调用方需要更新。
|
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 [
|
|
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) 或 [
|
|
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
|
-
|
|
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`
|
|
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
|
|
176
|
+
报告记录实际解析版本,便于比较默认组合和旧版本组合。
|
|
177
|
+
开发锁文件均忽略;gemspec 不限定依赖版本,基准、夹具、兼容 Gemfile 和审阅报告均不进 gem。
|
|
178
178
|
|
|
179
179
|
gem 只收录库代码、TextFSM 模板、CLI、双语设备/Netdisco 指南、架构/验证/发布文档、双语 README、LICENSE、
|
|
180
180
|
CHANGELOG、SECURITY 和 CONTRIBUTING。仅两个非敏感 YAML 示例随包分发;测试、示例脚本、发布工具、工作流、本地评审快照、本地配置和备份不进入包。
|
|
@@ -255,6 +255,15 @@ bundle exec ruby script/benchmarks/report_workload.rb 100000 20
|
|
|
255
255
|
|
|
256
256
|
脚本分别输出批次构造、首次统计及重复统计的单调时钟耗时与对象分配数;合成结果的创建不计入测量。比较两个源码版本时,在同一 Ruby 与主机上各运行至少三次。批次构造会复制结果容器并累计状态,重复统计复用计数;不读取报告的调用方也承担构造成本。TFTP 文件核验与逐设备明细仍有线性成本,此基准不测网络吞吐或现场设备性能。
|
|
257
257
|
|
|
258
|
+
## 清单连接与信任文件合并基准
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
bundle exec ruby script/benchmarks/http_workload.rb 200 5
|
|
262
|
+
bundle exec ruby script/benchmarks/known_hosts_workload.rb 30000 3000 5
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
HTTP 基准使用本地 TCP 服务返回 200 页和空结束页,记录总耗时、分配数和连接数,并验证清单内容与顺序;known_hosts 基准包含真实 ssh-keygen 检查和文件提交,验证旧条目及新增条目完整保序。两者均不包含设备登录或现场网络吞吐,比较时须保持相同 Ruby、主机、输入和运行次数。`test/netdisco_budget_test.rb` 另覆盖认证和重连共用总期限、慢速响应头、超额正文及取消后的 socket/观察线程回收。
|
|
266
|
+
|
|
258
267
|
## 厂商能力与模板一致性
|
|
259
268
|
|
|
260
269
|
`test/capabilities_test.rb` 离线核对全部已注册平台的能力;`test/vendor_topology_parity_test.rb` 覆盖 Huawei LLDP 简表与接口描述、FortiOS 顶层接口描述/嵌套表隔离,以及 Junos 物理接口和逻辑单元描述。`test/textfsm_test.rb` 同时检查 H3C 两种列序在通用命令解析入口中的语义、内置厂商/命令的完整匹配,以及各配置方言的索引入口。完整性拒绝路径覆盖缺失表头、残缺记录、未闭合接口表/引号、VDOM 作用域不明和没有值的 Junos description。
|
data/docs/architecture.md
CHANGED
|
@@ -79,6 +79,8 @@ Fleet 在规范目标校验、凭据解析及连接器构造之前加同一把
|
|
|
79
79
|
|
|
80
80
|
`Storage::AtomicFile.write` 统一返回 `Receipt`,执行临时文件写入、flush/fsync、rename、父目录 fsync,并跟踪 not_committed/committed/durable 和出错阶段。rename 前失败保留旧文件;之后失败保留新文件。目录 fsync 的 EINVAL/ENOSYS/ENOTSUP/EOPNOTSUPP/NotImplementedError 单独表示不支持;其他错误不能降级为成功。durable 只表示同步调用成功,未变内容不补验过去写入,断电恢复也未在本地测试中验证。
|
|
81
81
|
|
|
82
|
+
`KnownHosts` 在跨进程锁内重读和合并已认证的会话密钥,条目索引保留原顺序及已有重复行,新增行只登记一次。提交复用 `AtomicFile`;目录同步失败保留已替换文件并报告 `file_persistence_unconfirmed`,不能触发再次登记。不安全的非普通共享信任文件报告 `known_hosts_unsafe`,strict 模式仍由 OpenSSH 管理。写入器仅在实际提交时加载,纯会话引擎加载不引入存储层。
|
|
83
|
+
|
|
82
84
|
提交后的 `BackupPersistenceError` 只携带原 `Backup` 元数据和受控写入回执,Fleet 保留产物并使用已有 saved_with_error 分类。只接受库定义的具体完成错误及匹配产物类型,第三方异常的 backup 属性或自定义 WriteError 子类不作为完成证据。报告保留已提交位置,导出错误说明 committed;设备命令不会自动重放。
|
|
83
85
|
|
|
84
86
|
直接备份在路径锁退出前保存完成元数据,锁关闭的普通故障转换为 `BackupFinalizeError`,其中 `backup` 保留结果,`underlying_type` 经过类型白名单。该错误只确认备份操作已完成,不制造文件同步回执;未变文件的历史持久性仍未补验。Fleet 的外层路径锁及 TFTP 归档锁同样保存最新结果,退出故障转为 saved_with_error 或 reported_with_error,已有设备诊断优先,中断继续传播。
|
|
@@ -135,7 +137,7 @@ Diagnostic 只保存固定词表中的码、类型、阶段和受控产物状态
|
|
|
135
137
|
|
|
136
138
|
`CLI::Backup` 在创建批次目录和读取凭据前固定并验证策略。已固定的 `Settings#with_overrides` 只复制非敏感快照上的覆盖值,不重新读取环境中的策略字段;后续 `snapshot(mode:)` 继续按当前业务模式校验,离线导出不会隐式转成网络备份校验。原凭据来源仍保持动态。
|
|
137
139
|
|
|
138
|
-
`Inventory::HTTP#devices` 每次持有独立 `Inventory::Budget`,认证、分页、QueryRequired 查询共同累计正文字节和去重前记录数,并共享单调时钟 deadline
|
|
140
|
+
`Inventory::HTTP#devices` 每次持有独立 `Inventory::Budget`,认证、分页、QueryRequired 查询共同累计正文字节和去重前记录数,并共享单调时钟 deadline。每次调用独占一个 HTTP 连接作用域和期限观察线程,认证与分页复用连接;服务端关闭 keep-alive 后,后续请求可以重新建连,但失败请求不自动重试。连接不存入来源实例,并发调用不共用传输状态。默认 HTTP 使用 `read_body`,先检查实际接收字节再追加,不信任 Content-Length。每阶段缩短原生连接/读/写超时;总期限还覆盖慢速响应头。期限观察线程只关闭本次拥有的 HTTP 连接,防止 chunked 的收尾读取拖延返回,调用结束即唤醒并 join。默认 HTTP 不做隐式 GET 重试;这一机制不进入设备命令路径。
|
|
139
141
|
|
|
140
142
|
注入的 `requester` 接收 URI 和 request。它返回后才接受长度和期限检查,不在任意用户 Ruby 回调中注入异步异常。解析和集合操作完成后也检查期限,但这不构成任意 CPU 回调的抢占保证。预算失败仅报告错误码与安全类型,没有部分清单、响应正文或底层 cause。HTTPS 的标准证书检查不变;默认允许明文 HTTP,可通过显式策略禁止。
|
|
141
143
|
|
|
@@ -326,7 +328,7 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
|
|
|
326
328
|
|
|
327
329
|
`engine/interaction`、`engine/dialogue`、`engine/response` 分别定义交互规则、对话语法与响应值;读取循环位于 `engine/response_reader`。`engine/errors` 不加载脱敏运行时,`engine/redactor` 负责秘密作用域。Session 明确加载并持有读取器与脱敏器。
|
|
328
330
|
|
|
329
|
-
Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb`
|
|
331
|
+
Netdisco 的 Settings、Planner、Fleet、CLI 等文件各自声明实际依赖,单独 require 后即可调用,不依赖 `netdisco.rb` 的预设加载顺序。总入口组合 CLI、CLI::Backup 和 CLI::Topology;Batch 通过 autoload 创建 Report,避免两者循环加载。内部清单预算 `Inventory::Budget` 由 Inventory 清单适配器加载。Progress 的终端显示、Fleet 的设备执行、Report 的结果解释和 Storage 的文件持久化保持独立职责。
|
|
330
332
|
|
|
331
333
|
公共策略位于 `RunningConfig::Strategy` / `Rendered`、`Tftp::Strategy` / `FileUpload`、`Topology::Strategy` / `ImmediateStrategy`。厂商文件直接依赖公共策略,由 Profile 绑定;没有反向加载和厂商转发别名。
|
|
332
334
|
|
|
@@ -357,12 +359,15 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
|
|
|
357
359
|
| 命令结果 | `Command::Result`,位于 `engine/command/result.rb`;脚本汇总仍为 `Result` |
|
|
358
360
|
| 安全诊断词表 | `Error::Metadata`,位于 `engine/error/metadata.rb` |
|
|
359
361
|
| 清单预算与缓存 | `Netdisco::Inventory::Budget/Cache`,位于 `netdisco/inventory/` |
|
|
362
|
+
| 配置解析与初始化 | `Netdisco::Config` 和 `Config::Initializer`,位于 `netdisco/config.rb` 和 `netdisco/config/initializer.rb` |
|
|
363
|
+
| 逐设备策略 | `Netdisco::Device::Policy`,位于 `netdisco/device/policy.rb`,按厂商默认和设备地址选取参数 |
|
|
364
|
+
| 拓扑批次参数 | `Netdisco::Topology::Options`,位于 `netdisco/topology/options.rb`;设备拓扑能力仍为 `Net::Connector::Topology` |
|
|
360
365
|
| 配置操作结果 | `Netdisco::Operations::Batch/Outcome`,位于 `netdisco/operations/` |
|
|
361
366
|
| 报告存储 | `Netdisco::Report::Store`,位于 `netdisco/report/store.rb`,通过 `report_store:` 注入 |
|
|
362
367
|
| 服务器上传证据 | `Netdisco::Tftp::Verifier/Archive`,位于 `netdisco/tftp/` |
|
|
363
368
|
| 厂商平台 | `Vendor::CiscoIos` 等平台类直接继承 Base 或协议家族类;策略位于平台类下 |
|
|
364
369
|
| 文件边界 | `Storage::FileReader` 与 `Storage::AtomicFile`,分别负责受控读取与原子替换 |
|
|
365
|
-
| 终端应用 | `Netdisco::CLI::Backup/Credentials/Options`,位于 `netdisco/cli/` |
|
|
370
|
+
| 终端应用 | `Netdisco::CLI::Backup/Topology/Credentials/Options`,位于 `netdisco/cli/` |
|
|
366
371
|
|
|
367
372
|
工厂方法用 `build_*` 表明返回新对象;抛错校验使用 `validate_*!`;块作用域使用明确的 `with_*`。单命令、脚本、命令文本构造和完成证据判断分别命名,避免从通用的 execute/check/complete 猜测行为。`legacy_ssh` 表示设备 SSH 协议协商选项,Netdisco QueryRequired 则是服务端 API 行为,两者均独立于本库旧版本接口。
|
|
368
373
|
|
|
@@ -406,7 +411,7 @@ RunningConfig 的两处受控 `send` 保留,用于私有策略绑定与 protec
|
|
|
406
411
|
|
|
407
412
|
业务执行与完成事件的异常范围分开:完成事件只尝试一次,观察者失败不能触发第二次相同通知。已有登录、命令或业务错误优先于失败日志的错误;脚本最终处理已生成的 Result.config 与 steps 不因日志收尾丢失。日志上下文刷新和传输接线恢复仍执行清理,但保留正在传播的主错误及 Interrupt。TFTP 的失败证据同样先决定错误码,再尝试记录失败事件。
|
|
408
413
|
|
|
409
|
-
错误归一化通过 `Error#with_diagnostics` 保留内置错误的业务回执,并新建异常的原生状态,避免复制原 cause
|
|
414
|
+
错误归一化通过 `Error#with_diagnostics` 保留内置错误的业务回执,并新建异常的原生状态,避免复制原 cause 或调用栈。复制还要求实际类与白名单常量身份一致,不能只凭可重写的类名取得信任。自定义错误子类使用脱敏后的参数重新构造,不能复制可能参与消息呈现的原始私有字段。错误输出持有独立冻结副本,不冻结调用方的原始缓冲。缺少必需设备提示符或抽象命令抛出 `ConfigurationError`,属于可隔离的普通故障。会话租约退出时若日志收尾失败,已返回的 Result 仍保留步骤、配置和原业务错误;仅在原操作成功时附加日志错误。
|
|
410
415
|
|
|
411
416
|
单条命令的完整响应在输出日志和完成事件之前保存;即使这些日志处理失败,Session 仍在脱敏作用域退出前把响应交给 Execution,保留已完成步骤与输出预算。TFTP 可从失败结果的完成步骤构造设备回执,再报告日志错误;不会据此重放命令。
|
|
412
417
|
|
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 [
|
|
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/
|
|
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.
|
data/docs/netdisco.zh-CN.md
CHANGED
|
@@ -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/
|
|
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/
|
|
120
|
-
net-backup --config examples/
|
|
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/
|
|
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
|
|
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
|
-
|
|
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/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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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/examples/backup.yml
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# 仅保存非敏感策略,凭据使用环境变量。
|
|
2
|
-
#
|
|
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:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
@@ -111,7 +111,7 @@ module Net
|
|
|
111
111
|
else
|
|
112
112
|
false
|
|
113
113
|
end
|
|
114
|
-
rescue
|
|
114
|
+
rescue ConfigurationError
|
|
115
115
|
false
|
|
116
116
|
end
|
|
117
117
|
|
|
@@ -200,7 +200,7 @@ module Net
|
|
|
200
200
|
prompt = profile.login_prompt
|
|
201
201
|
return prompt if prompt
|
|
202
202
|
|
|
203
|
-
raise
|
|
203
|
+
raise ConfigurationError, "#{self.class} must define login_prompt"
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
# 返回普通命令提示;厂商必须实现。
|
|
@@ -208,7 +208,7 @@ module Net
|
|
|
208
208
|
prompt = profile.command_prompt
|
|
209
209
|
return prompt if prompt
|
|
210
210
|
|
|
211
|
-
raise
|
|
211
|
+
raise ConfigurationError, "#{self.class} must define command_prompt"
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
# 登录成功后执行厂商初始化钩子。
|
|
@@ -18,7 +18,7 @@ module Net
|
|
|
18
18
|
commands = profile.config_commands
|
|
19
19
|
return commands if commands
|
|
20
20
|
|
|
21
|
-
raise
|
|
21
|
+
raise ConfigurationError, "#{self.class} must define running configuration commands"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# 子类可覆盖并调用 super,复用本次响应检查积累的策略状态。
|
|
@@ -29,7 +29,11 @@ module Net
|
|
|
29
29
|
ensure
|
|
30
30
|
begin
|
|
31
31
|
# 二次异步取消须等待关闭完成,不能在收尾中再次丢失资源所有权。
|
|
32
|
-
Thread.handle_interrupt(Exception => :never)
|
|
32
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
33
|
+
connector&.close
|
|
34
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 在恢复交付待处理中断前保存同步关闭故障。
|
|
35
|
+
failure ||= error
|
|
36
|
+
end
|
|
33
37
|
rescue Exception => error # rubocop:disable Lint/RescueException -- 关闭故障不能覆盖原操作故障,包括 Interrupt。
|
|
34
38
|
failure ||= error
|
|
35
39
|
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,10 +47,14 @@ 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, **)
|
|
52
|
-
raise
|
|
57
|
+
raise ConfigurationError, "TFTP strategy must provide a transfer command"
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def transfer_interactions(_target, **) = []
|
|
@@ -11,8 +11,8 @@ module Net
|
|
|
11
11
|
module Capability
|
|
12
12
|
# 要求设备直接向 TFTP 服务器导出原生配置。
|
|
13
13
|
# 完成仅表示设备报告传输成功,未读取服务器端文件。
|
|
14
|
-
def tftp_backup(host:, path: nil, source_file: nil, vrf: nil)
|
|
15
|
-
Tftp.new(self).call(host: host, path: path, source_file: source_file, vrf: vrf)
|
|
14
|
+
def tftp_backup(host:, path: nil, source_file: nil, vrf: nil, source_interface: nil)
|
|
15
|
+
Tftp.new(self).call(host: host, path: path, source_file: source_file, vrf: vrf, source_interface: source_interface)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ module Net
|
|
|
20
20
|
def initialize(device) = @device = device
|
|
21
21
|
|
|
22
22
|
# 参数先纯校验;源探测、上传、证据核对及收尾共用一次会话租约。
|
|
23
|
-
def call(host:, path: nil, source_file: nil, vrf: nil)
|
|
23
|
+
def call(host:, path: nil, source_file: nil, vrf: nil, source_interface: nil)
|
|
24
24
|
receipt = nil
|
|
25
25
|
strategy_class = @device.profile.tftp_strategy
|
|
26
26
|
unless strategy_class
|
|
@@ -34,12 +34,21 @@ module Net
|
|
|
34
34
|
source_file = TftpTarget.validate_source_file!(source_file).dup.freeze unless source_file.nil?
|
|
35
35
|
vrf = vrf&.dup&.freeze
|
|
36
36
|
target = TftpTarget.new(host: host, path: path.nil? ? "preflight.cfg" : path, explicit_path: !path.nil?)
|
|
37
|
+
source_interface = source_interface.dup.freeze if source_interface.is_a?(String)
|
|
38
|
+
unless source_interface.nil?
|
|
39
|
+
unless strategy.respond_to?(:validate_source_interface!)
|
|
40
|
+
raise ArgumentError, "source_interface is not supported by this TFTP strategy"
|
|
41
|
+
end
|
|
42
|
+
strategy.validate_source_interface!(target, source_interface: source_interface)
|
|
43
|
+
end
|
|
37
44
|
strategy.validate_options!(target, source_file: source_file, vrf: vrf)
|
|
38
45
|
@device.with_operation(:tftp_backup) do
|
|
39
46
|
source = strategy.resolve_source_file(source_file)
|
|
40
47
|
source = TftpTarget.validate_source_file!(source).dup.freeze unless source.nil?
|
|
41
48
|
target = TftpTarget.new(host: target.host, path: strategy.default_path(source), explicit_path: false) if path.nil?
|
|
42
|
-
|
|
49
|
+
transfer_options = { source_file: source, vrf: vrf }
|
|
50
|
+
transfer_options[:source_interface] = source_interface unless source_interface.nil?
|
|
51
|
+
result = @device.execute_operation(strategy.script(target, **transfer_options),
|
|
43
52
|
name: :tftp_backup, privilege: false)
|
|
44
53
|
confirm_transfer!(strategy, target, result)
|
|
45
54
|
# 一旦看到明确完成证据就保存回执;路径解析或用户钩子失败也不能抹去上传事实。
|