net-connector 0.18.1 → 0.20.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 +12 -0
- data/README.md +20 -1
- data/README.zh-CN.md +1 -1
- data/docs/VERIFICATION.md +9 -10
- data/docs/usage.md +1 -1
- data/docs/usage.zh-CN.md +2 -2
- data/lib/net/connector/credentials.rb +39 -0
- data/lib/net/connector/netdisco/catalog.rb +53 -0
- data/lib/net/connector/netdisco/fleet.rb +5 -28
- data/lib/net/connector/netdisco/settings.rb +1 -7
- data/lib/net/connector/netdisco/ssh/archive/worker.py +15 -0
- data/lib/net/connector/version.rb +1 -1
- data/lib/net/connector.rb +1 -0
- metadata +22 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f04a6944e8273ad350cc4ec2af6f7c1673d40d0bd75a52ee4068c0c5086047c
|
|
4
|
+
data.tar.gz: c7cb714084fdc5edaca889c3261af88398167db99d181bbfb6dc0db596c45325
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f044f6b70db2ca9129878e7e7cc5d8e1ad4ec8e0984c7977ac266f3d86731712be06ac6c97ce86ff5e203c72bc11ea7b19460993fceb145f02350de568961520
|
|
7
|
+
data.tar.gz: 03e67f261dcbe821f673bee9ac5df06bd82371e23f7effdac6fc2f39ac29e487f2c946591d749d8cff2060f7517240255cebcb851c2e5f74b32cbc48ff1bdd63
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.20.0 - 2026-10-29
|
|
6
|
+
|
|
7
|
+
- Require the latest stable TextFSM 0.5.1, align the minimum dependency matrix, and verify JSON 3 with its original published package instead of a source metadata patch.
|
|
8
|
+
|
|
9
|
+
- Recheck streamed archive content on the pinned descriptor so same-timestamp overwrites cannot receive a successful copy or delivery receipt.
|
|
10
|
+
|
|
11
|
+
- Bound compatible ctx_cli and expect-pty dependencies to the contract versions tested with this release (ctx_cli 0.4.x and expect-pty 0.9.6 through 0.9.x).
|
|
12
|
+
|
|
13
|
+
## 0.19.0 - 2026-10-29
|
|
14
|
+
|
|
15
|
+
- Extract `Netdisco::Catalog#devices(policy:)` for immutable inventory without credentials or operations; Fleet delegates inventory access. Add live, injected `Credentials#resolve(platform:)` with platform-specific username/password/enable-password precedence and independent secret snapshots.
|
|
16
|
+
|
|
5
17
|
## 0.18.1 - 2026-10-24
|
|
6
18
|
|
|
7
19
|
- Stream SSH archive bodies through bounded binary frames and incremental hashes instead of whole-file base64 copies; preserve deadlines, verified server receipts and committed local paths.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Ruby sessions for network device CLIs over SSH or Telnet. Collect configurations, save private backups, run interactive scripts, parse output with TextFSM, and back up fleets from Netdisco inventories.
|
|
6
6
|
|
|
7
|
-
Requires Ruby 3.4+, expect-pty 0.9.4+ and a POSIX system. SSH uses local OpenSSH; Telnet requires a local `telnet` executable and explicit configuration.
|
|
7
|
+
Requires Ruby 3.4+, expect-pty 0.9.6+, ctx_cli 0.4.x, TextFSM 0.5.1+ and a POSIX system. SSH uses local OpenSSH; Telnet requires a local `telnet` executable and explicit configuration.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -66,3 +66,22 @@ Licensed under [MIT](LICENSE).
|
|
|
66
66
|
## ctx_cli 公共入口
|
|
67
67
|
|
|
68
68
|
宿主配置文件读取、私有文件初始化、公共参数与终端交互由 ctx_cli 维护。原 `Net::Connector::Config`、配置路径、优先级和 backup/TFTP/topology 策略继续保留。源码联调使用 `CTX_CLI_PATH=../ctx_cli`;隔离安装可通过 `CTX_CLI_GEM=/absolute/path/ctx_cli-0.1.0.gem` 传入精确候选。
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## Inventory and credentials without device operations
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
require "net/connector/netdisco/catalog"
|
|
75
|
+
require "net/connector/credentials"
|
|
76
|
+
|
|
77
|
+
settings = Net::Connector::Netdisco::Settings.new(env: ENV)
|
|
78
|
+
policy = settings.snapshot(mode: :inventory)
|
|
79
|
+
catalog = Net::Connector::Netdisco::Catalog.new(settings: settings)
|
|
80
|
+
devices = catalog.devices(policy: policy)
|
|
81
|
+
# Inspect device.issue before selecting targets; duplicate rows are retained.
|
|
82
|
+
credentials = Net::Connector::Credentials.new(env: ENV).resolve(platform: :cisco_nxos)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Catalog owns inventory reading, normalization, duplicate marking and cache status (`#status`). It does not resolve device credentials, log into devices or create operation reports. Pass one policy snapshot throughout a run. Fleet delegates to Catalog and retains operation orchestration.
|
|
86
|
+
|
|
87
|
+
Credentials reads the injected environment on every `resolve`; it returns an independent frozen hash, or nil when no username is available. Platform-specific `NC_<PLATFORM>_USERNAME`, `_PASSWORD`, and `_ENABLE_PASSWORD` override `NC_DEVICE_USERNAME`, `NC_DEVICE_PASSWORD`, and `NC_ENABLE_PASSWORD` respectively. Blank values fall back. Secrets are never cached or included in object inspection.
|
data/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
通过 SSH 或 Telnet 操作网络设备的 Ruby 库。支持配置采集、私有备份、交互脚本、TextFSM 解析,以及基于 Netdisco 清单的批量备份。
|
|
6
6
|
|
|
7
|
-
需要 Ruby 3.4+、expect-pty 0.9.4+ 和 POSIX 系统。SSH 使用本机 OpenSSH;Telnet 需要本机安装 `telnet` 并显式配置。
|
|
7
|
+
需要 Ruby 3.4+、expect-pty 0.9.6+、ctx_cli 0.4.x、TextFSM 0.5.1+ 和 POSIX 系统。SSH 使用本机 OpenSSH;Telnet 需要本机安装 `telnet` 并显式配置。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
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
|
-
PTY 与共享脱敏直接依赖 expect-pty 0.9.
|
|
158
|
+
PTY 与共享脱敏直接依赖 expect-pty 0.9.6+ 的公开 `Session`、`buffer_bytesize` 和 `Expect::Redactor` 接口。
|
|
159
159
|
开发用 `parallel` 保持 1.x。升级 expect-pty 时先验证源码集成,再用本次构建的 gem 验证隔离安装;
|
|
160
160
|
新依赖版本发布前不得将本地构建验证记成已发布版本验收,也不能退回旧接口掩盖依赖缺失。
|
|
161
161
|
|
|
@@ -163,7 +163,7 @@ PTY 与共享脱敏直接依赖 expect-pty 0.9.4+ 的公开 `Session`、`buffer_
|
|
|
163
163
|
分别解析兼容依赖。应用使用者应在自己的应用中提交 lockfile。测试和打包
|
|
164
164
|
脚本通过隔离安装检查运行依赖,避免依赖开发环境里偶然存在的 gem。
|
|
165
165
|
|
|
166
|
-
`gemfiles/minimum.gemfile` 固定受支持的依赖下界 expect-pty 0.9.4、textfsm 0.
|
|
166
|
+
`gemfiles/minimum.gemfile` 固定受支持的依赖下界 expect-pty 0.9.6、ctx_cli 0.4.0、textfsm 0.5.1;
|
|
167
167
|
标准库和开发工具仍按主 Gemfile 的兼容范围解析。普通矩阵检查允许版本的正常解析,
|
|
168
168
|
另在 Ubuntu / Ruby 3.4、4.0 检查这组版本的全量测试与隔离安装。本地复现:
|
|
169
169
|
|
|
@@ -206,17 +206,16 @@ Gitleaks 默认规则之外,还检查网络设备密码/SNMP community 配置
|
|
|
206
206
|
扫描工具不会自动改写 Git 历史或真实设备备份。规则用于拦截常见泄露,
|
|
207
207
|
发布前仍需人工确认设备名称、拓扑和业务配置等上下文信息是否适合公开。
|
|
208
208
|
|
|
209
|
-
## JSON 2
|
|
209
|
+
## 使用已发布 TextFSM 分别验证 JSON 2 与 JSON 3
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
运行依赖要求 TextFSM 0.5.1 或更新版本;0.5.1 的公开 JSON 声明为 `>= 2.0`,
|
|
212
|
+
允许普通消费者选择 JSON 2 或 JSON 3。最低依赖任务固定 TextFSM 0.5.1 与 JSON 2。
|
|
213
213
|
|
|
214
|
-
CI 的 `json3-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
该任务明确代表尚未发布依赖声明下的源码兼容性;不自动发布、替换正式依赖或修改用户的全局 gem。
|
|
214
|
+
CI 的 `json3-compatibility` 在 Ruby 3.4 / 4.0 使用已发布 TextFSM 0.5.1 与 JSON 3,
|
|
215
|
+
执行完整测试和真实打包隔离安装。脚本核对原始 gem 的 JSON 依赖声明,再将发布包缓存
|
|
216
|
+
交给隔离消费者;不再检出旧源码、修改 metadata 或重建同版本 TextFSM。
|
|
218
217
|
|
|
219
|
-
|
|
218
|
+
本地复现:
|
|
220
219
|
|
|
221
220
|
```sh
|
|
222
221
|
BUNDLE_GEMFILE=gemfiles/json3.gemfile bundle install
|
data/docs/usage.md
CHANGED
|
@@ -165,6 +165,6 @@ Per-command prompts use `interactions: [Net::Connector::Interaction.new(/Token:\
|
|
|
165
165
|
|
|
166
166
|
## Dependencies and extensions
|
|
167
167
|
|
|
168
|
-
Load device APIs with `require "net/connector"`; Netdisco has a separate entrypoint. Vendor implementations and TextFSM load on demand. The gemspec requires expect-pty 0.9.4+; the host application's lockfile selects concrete dependency versions. The minimum dependency matrix uses TextFSM 0.
|
|
168
|
+
Load device APIs with `require "net/connector"`; Netdisco has a separate entrypoint. Vendor implementations and TextFSM load on demand. The gemspec requires expect-pty 0.9.6+, ctx_cli 0.4.x and TextFSM 0.5.1+; the host application's lockfile selects concrete dependency versions. The minimum dependency matrix uses TextFSM 0.5.1 and JSON 2. A separate JSON 3 matrix uses the same published TextFSM package without modifying its dependency metadata.
|
|
169
169
|
|
|
170
170
|
CLI entry points share layered configuration and `.env` loading; dotenv is a declared runtime dependency. Core library calls do not discover host files. Follow the [loading and migration reference](architecture.md#加载入口与厂商策略) (Chinese) for custom profiles, strategies, and protected hooks.
|
data/docs/usage.zh-CN.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](usage.md) | [返回首页](../README.zh-CN.md) | [Netdisco 批量备份](netdisco.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
支持 Ruby 3.4 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。要求 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.9.4
|
|
5
|
+
支持 Ruby 3.4 及以上版本和 POSIX 系统。SSH 调用本机 OpenSSH,Telnet 需要本机安装 `telnet` 并显式选择。要求 [`expect-pty`](https://rubygems.org/gems/expect-pty) 0.9.6 及以上、ctx_cli 0.4.x 和 [`textfsm`](https://rubygems.org/gems/textfsm) 0.5.1 及以上;具体版本由宿主锁定。
|
|
6
6
|
|
|
7
7
|
脱敏直接复用 expect-pty 的公开 `Expect::Redactor` 接口;连接器只管理秘密作用域和配置输出的隐私策略。
|
|
8
8
|
|
|
9
|
-
gemspec
|
|
9
|
+
gemspec 声明所需接口的版本下界;宿主应用通过自己的 Gemfile 和锁文件固定具体依赖版本。最低依赖组合使用 textfsm 0.5.1 与 JSON 2;JSON 3 矩阵也使用同一已发布 TextFSM 包,无需源码或 metadata 补丁。开发工具及测试矩阵的版本约束不进入 gem 的运行依赖。
|
|
10
10
|
|
|
11
11
|
CLI 与示例入口共用分层配置和 `.env` 加载,dotenv 已声明为运行时依赖;核心库调用不自动发现宿主文件。
|
|
12
12
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module Connector
|
|
5
|
+
# 设备凭据的字段优先级;借用来源,每次解析独立快照,不缓存秘密。
|
|
6
|
+
class Credentials
|
|
7
|
+
def initialize(env: ENV)
|
|
8
|
+
raise ArgumentError, "Credential source must support key lookup" unless env.respond_to?(:[])
|
|
9
|
+
@env = env
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# 平台字段优先于通用字段;缺少用户名时由宿主决定跳过或拒绝。
|
|
13
|
+
def resolve(platform:)
|
|
14
|
+
unless (platform.is_a?(String) || platform.is_a?(Symbol)) && /\A[a-z][a-z0-9_]*\z/i.match?(platform.to_s)
|
|
15
|
+
raise ArgumentError, "Credential platform must be a name"
|
|
16
|
+
end
|
|
17
|
+
prefix = "NC_#{platform.to_s.upcase}_"
|
|
18
|
+
username = value(prefix + "USERNAME") || value("NC_DEVICE_USERNAME")
|
|
19
|
+
return nil unless username
|
|
20
|
+
|
|
21
|
+
{ username: username, password: value(prefix + "PASSWORD") || value("NC_DEVICE_PASSWORD"),
|
|
22
|
+
enable_password: value(prefix + "ENABLE_PASSWORD") || value("NC_ENABLE_PASSWORD") }.freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inspect = "#<#{self.class}>"
|
|
26
|
+
alias to_s inspect
|
|
27
|
+
def pretty_print(printer) = printer.text(inspect)
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# 拒绝非法值而不回显内容,也不冻结调用方的字符串。
|
|
32
|
+
def value(key)
|
|
33
|
+
result = @env[key]
|
|
34
|
+
raise ArgumentError, "#{key} must be a String" unless result.nil? || result.is_a?(String)
|
|
35
|
+
result.dup.freeze unless result.nil? || result.strip.empty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "settings"
|
|
4
|
+
require_relative "device"
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module Connector
|
|
8
|
+
module Netdisco
|
|
9
|
+
# 只读取、归一化及标记清单;凭据、连接、操作和报告由宿主拥有。
|
|
10
|
+
class Catalog
|
|
11
|
+
attr_reader :status
|
|
12
|
+
|
|
13
|
+
def initialize(settings: Settings.new, inventory: nil, rules: nil)
|
|
14
|
+
@settings, @inventory, @rules = settings, inventory, rules
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# 使用调用方固定的同批策略,保留被过滤、无效及重复的记录。
|
|
18
|
+
def devices(policy:, force_refresh: false)
|
|
19
|
+
@status = nil
|
|
20
|
+
raise ArgumentError, "force_refresh must be true or false" unless [true, false].include?(force_refresh)
|
|
21
|
+
|
|
22
|
+
inventory = @inventory || @settings.inventory(policy: policy)
|
|
23
|
+
cache_mode = policy.inventory_cache_mode
|
|
24
|
+
raise ArgumentError, "cache-only cannot force refresh" if cache_mode == :only && force_refresh
|
|
25
|
+
if cache_mode == :only && !inventory.is_a?(Inventory::Cache)
|
|
26
|
+
raise Inventory::Error.new("validated inventory cache is unavailable", code: :cache_unavailable), cause: nil
|
|
27
|
+
end
|
|
28
|
+
rows = if inventory.is_a?(Inventory::Cache)
|
|
29
|
+
inventory.devices(force_refresh: force_refresh, cache_mode: cache_mode)
|
|
30
|
+
else
|
|
31
|
+
inventory.devices
|
|
32
|
+
end
|
|
33
|
+
@status = if inventory.is_a?(Inventory::Cache)
|
|
34
|
+
{ source: inventory.status, fetched_at: inventory.fetched_at,
|
|
35
|
+
refresh_error_code: inventory.refresh_error_code, write_error_type: inventory.write_error_type }.freeze
|
|
36
|
+
else
|
|
37
|
+
{ source: :uncached }.freeze
|
|
38
|
+
end
|
|
39
|
+
raise Inventory::Error, "Netdisco returned an invalid device inventory" unless rows.is_a?(Array)
|
|
40
|
+
|
|
41
|
+
rules = @rules || policy.rules
|
|
42
|
+
devices = rows.map { |row| Device.from_row(row, rules: rules) }
|
|
43
|
+
host_counts = devices.each_with_object(Hash.new(0)) { |device, counts| counts[device.host] += 1 if device.host }
|
|
44
|
+
devices.map { |device| host_counts[device.host] > 1 ? device.with(issue: :duplicate_host) : device }.freeze
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def inspect = "#<#{self.class}>"
|
|
48
|
+
alias to_s inspect
|
|
49
|
+
def pretty_print(printer) = printer.text(inspect)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require_relative "../storage"
|
|
5
5
|
require_relative "../device/scope"
|
|
6
6
|
require_relative "inventory"
|
|
7
|
+
require_relative "catalog"
|
|
7
8
|
require_relative "settings"
|
|
8
9
|
require_relative "device"
|
|
9
10
|
require_relative "device/access"
|
|
@@ -35,8 +36,7 @@ module Net
|
|
|
35
36
|
def initialize(inventory: nil, settings: Settings.new, rules: nil, credentials: nil, connector_factory: nil,
|
|
36
37
|
report_store: Report::Store::Json.new)
|
|
37
38
|
@settings = settings
|
|
38
|
-
@
|
|
39
|
-
@rules = rules
|
|
39
|
+
@catalog = Catalog.new(settings: settings, inventory: inventory, rules: rules)
|
|
40
40
|
@credentials = credentials
|
|
41
41
|
@connector_factory = connector_factory || ->(device, access_options) { device.build_connector(**access_options) }
|
|
42
42
|
@report_store = report_store
|
|
@@ -176,32 +176,9 @@ module Net
|
|
|
176
176
|
|
|
177
177
|
# 策略在清单请求之前固定;注入的清单来源仍由调用方负责其自身生命周期。
|
|
178
178
|
def inventory(settings_snapshot, force_refresh: false)
|
|
179
|
-
@
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
inventory = @inventory || @settings.inventory(policy: settings_snapshot)
|
|
183
|
-
cache_mode = settings_snapshot.inventory_cache_mode
|
|
184
|
-
raise ArgumentError, "cache-only cannot force refresh" if cache_mode == :only && force_refresh
|
|
185
|
-
if cache_mode == :only && !inventory.is_a?(Inventory::Cache)
|
|
186
|
-
raise Inventory::Error.new("validated inventory cache is unavailable", code: :cache_unavailable), cause: nil
|
|
187
|
-
end
|
|
188
|
-
rows = if inventory.is_a?(Inventory::Cache)
|
|
189
|
-
inventory.devices(force_refresh: force_refresh, cache_mode: cache_mode)
|
|
190
|
-
else
|
|
191
|
-
inventory.devices
|
|
192
|
-
end
|
|
193
|
-
@inventory_status = if inventory.is_a?(Inventory::Cache)
|
|
194
|
-
{ source: inventory.status, fetched_at: inventory.fetched_at,
|
|
195
|
-
refresh_error_code: inventory.refresh_error_code, write_error_type: inventory.write_error_type }.freeze
|
|
196
|
-
else
|
|
197
|
-
{ source: :uncached }.freeze
|
|
198
|
-
end
|
|
199
|
-
raise Inventory::Error, "Netdisco returned an invalid device inventory" unless rows.is_a?(Array)
|
|
200
|
-
|
|
201
|
-
rules = @rules || settings_snapshot.rules
|
|
202
|
-
devices = rows.map { |row| Device.from_row(row, rules: rules) }
|
|
203
|
-
host_counts = devices.each_with_object(Hash.new(0)) { |device, counts| counts[device.host] += 1 if device.host }
|
|
204
|
-
devices.map { |device| host_counts[device.host] > 1 ? device.with(issue: :duplicate_host) : device }.freeze
|
|
179
|
+
@catalog.devices(policy: settings_snapshot, force_refresh: force_refresh)
|
|
180
|
+
ensure
|
|
181
|
+
@inventory_status = @catalog.status
|
|
205
182
|
end
|
|
206
183
|
|
|
207
184
|
# 在固定清单快照上生成模式对应的计划,规划阶段不读取设备凭据。
|
|
@@ -346,13 +346,7 @@ module Net
|
|
|
346
346
|
|
|
347
347
|
# 每台任务读取最新厂商凭据,缺失字段回退到通用凭据;无用户名时跳过设备。
|
|
348
348
|
def device_credentials_for(device)
|
|
349
|
-
|
|
350
|
-
username = secret_value(prefix + "USERNAME") || secret_value("NC_DEVICE_USERNAME")
|
|
351
|
-
return nil unless username
|
|
352
|
-
|
|
353
|
-
{ username: username,
|
|
354
|
-
password: secret_value(prefix + "PASSWORD") || secret_value("NC_DEVICE_PASSWORD"),
|
|
355
|
-
enable_password: secret_value(prefix + "ENABLE_PASSWORD") || secret_value("NC_ENABLE_PASSWORD") }
|
|
349
|
+
Net::Connector::Credentials.new(env: @env).resolve(platform: device.vendor)
|
|
356
350
|
end
|
|
357
351
|
|
|
358
352
|
# 会话日志目录属于公开策略;调用方和批次入口负责实际创建。
|
|
@@ -130,6 +130,21 @@ def read_file(parent, name, missing=False, consume=None):
|
|
|
130
130
|
digest.update(chunk)
|
|
131
131
|
if consume is not None and consume(chunk) != len(chunk):
|
|
132
132
|
raise IOError("incomplete file write")
|
|
133
|
+
# 同一时钟粒度内的覆写可能不改变 stat 时间;投递后重读摘要核对已发送字节。
|
|
134
|
+
if consume is not None:
|
|
135
|
+
f.seek(0)
|
|
136
|
+
verified = hashlib.sha256()
|
|
137
|
+
verified_size = 0
|
|
138
|
+
while True:
|
|
139
|
+
chunk = f.read(CHUNK_BYTES)
|
|
140
|
+
if not chunk:
|
|
141
|
+
break
|
|
142
|
+
verified_size += len(chunk)
|
|
143
|
+
if verified_size > MAX_BYTES:
|
|
144
|
+
raise ValueError("oversized file")
|
|
145
|
+
verified.update(chunk)
|
|
146
|
+
if verified_size != size or verified.digest() != digest.digest():
|
|
147
|
+
raise ValueError("file changed while delivering")
|
|
133
148
|
after = os.fstat(f.fileno())
|
|
134
149
|
if size != after.st_size or fingerprint(before) != fingerprint(after):
|
|
135
150
|
raise ValueError("file changed while reading")
|
data/lib/net/connector.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: net-connector
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- net-connector contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ctx_cli
|
|
@@ -15,42 +15,54 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 0.4.0
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0.5'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
28
|
+
version: 0.4.0
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '0.5'
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: expect-pty
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
29
35
|
requirements:
|
|
30
36
|
- - ">="
|
|
31
37
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
38
|
+
version: 0.9.6
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0.10'
|
|
33
42
|
type: :runtime
|
|
34
43
|
prerelease: false
|
|
35
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
45
|
requirements:
|
|
37
46
|
- - ">="
|
|
38
47
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
48
|
+
version: 0.9.6
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0.10'
|
|
40
52
|
- !ruby/object:Gem::Dependency
|
|
41
53
|
name: textfsm
|
|
42
54
|
requirement: !ruby/object:Gem::Requirement
|
|
43
55
|
requirements:
|
|
44
56
|
- - ">="
|
|
45
57
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
58
|
+
version: 0.5.1
|
|
47
59
|
type: :runtime
|
|
48
60
|
prerelease: false
|
|
49
61
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
62
|
requirements:
|
|
51
63
|
- - ">="
|
|
52
64
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
65
|
+
version: 0.5.1
|
|
54
66
|
- !ruby/object:Gem::Dependency
|
|
55
67
|
name: pg
|
|
56
68
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -375,6 +387,7 @@ files:
|
|
|
375
387
|
- lib/net/connector.rb
|
|
376
388
|
- lib/net/connector/collect/progress.rb
|
|
377
389
|
- lib/net/connector/config.rb
|
|
390
|
+
- lib/net/connector/credentials.rb
|
|
378
391
|
- lib/net/connector/device/base.rb
|
|
379
392
|
- lib/net/connector/device/interfaces.rb
|
|
380
393
|
- lib/net/connector/device/interfaces/audit.rb
|
|
@@ -436,6 +449,7 @@ files:
|
|
|
436
449
|
- lib/net/connector/inventory/selection.rb
|
|
437
450
|
- lib/net/connector/netdisco.rb
|
|
438
451
|
- lib/net/connector/netdisco/batch.rb
|
|
452
|
+
- lib/net/connector/netdisco/catalog.rb
|
|
439
453
|
- lib/net/connector/netdisco/cli.rb
|
|
440
454
|
- lib/net/connector/netdisco/cli/backup.rb
|
|
441
455
|
- lib/net/connector/netdisco/cli/credentials.rb
|