net-connector 0.18.1 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87b108e7525f64a9df67db08537eafa2e3c2845797fccea6fe09fdb5701696fd
4
- data.tar.gz: f6c6bea9be822222feb3056e2866b39d35209b4fe06b108613c059261b987467
3
+ metadata.gz: fe3f1f3bc71cf02e0007ec892699ea03fb13f30b697f5e318ca256159e77a466
4
+ data.tar.gz: 7f2ed60b91541c0eb362b1dc1c063661f5f22eac07719f1ed9d56f552a7d175b
5
5
  SHA512:
6
- metadata.gz: de0022df96e76aeeec4bd916c7abd27f9fb4646269d552846d07d1c575a05593fbf3605a25f271574a48202e81100986fb5508e44c162c6b1668c158bcad9ae9
7
- data.tar.gz: 749ba87f2702d94098e02b3cf7f9fae6e00557f219c796e453028b7d67db07f1c7469985ad8275818de71b50a1bfc7b2319936e502817f7057f5b3a59c670cd7
6
+ metadata.gz: 03ea333dc6fdc10956b58478de7b67d8e3061345b3d96af8601a847dd393b158cd0bdbd7e4ca3ebc69c7cdca03d58ced76e34964b0370d9b2b0dad27c4d1f9dc
7
+ data.tar.gz: 38784dee207a4b5161add61183ebb49cf7b4a7a843171a29da2effe393105cd9c4bd00f2a0d3780ab296c430149a43792c85b9054375cb1c97269e89b1ad7f96
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.19.0 - 2026-10-29
6
+
7
+ - 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.
8
+
5
9
  ## 0.18.1 - 2026-10-24
6
10
 
7
11
  - 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
@@ -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.
@@ -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
- @inventory = inventory
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
- @inventory_status = nil
180
- raise ArgumentError, "force_refresh must be true or false" unless [true, false].include?(force_refresh)
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
- prefix = "NC_#{device.vendor.to_s.upcase}_"
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
  # 会话日志目录属于公开策略;调用方和批次入口负责实际创建。
@@ -3,6 +3,6 @@
3
3
  module Net
4
4
  module Connector
5
5
  # gemspec、CLI 与发布工具共用此版本,运行时读取不触发设备依赖加载。
6
- VERSION = "0.18.1"
6
+ VERSION = "0.19.0"
7
7
  end
8
8
  end
data/lib/net/connector.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "connector/version"
4
+ require_relative "connector/credentials"
4
5
  require_relative "connector/device/base"
5
6
  require_relative "connector/vendor"
6
7
 
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.18.1
4
+ version: 0.19.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-24 00:00:00.000000000 Z
10
+ date: 2026-10-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ctx_cli
@@ -375,6 +375,7 @@ files:
375
375
  - lib/net/connector.rb
376
376
  - lib/net/connector/collect/progress.rb
377
377
  - lib/net/connector/config.rb
378
+ - lib/net/connector/credentials.rb
378
379
  - lib/net/connector/device/base.rb
379
380
  - lib/net/connector/device/interfaces.rb
380
381
  - lib/net/connector/device/interfaces/audit.rb
@@ -436,6 +437,7 @@ files:
436
437
  - lib/net/connector/inventory/selection.rb
437
438
  - lib/net/connector/netdisco.rb
438
439
  - lib/net/connector/netdisco/batch.rb
440
+ - lib/net/connector/netdisco/catalog.rb
439
441
  - lib/net/connector/netdisco/cli.rb
440
442
  - lib/net/connector/netdisco/cli/backup.rb
441
443
  - lib/net/connector/netdisco/cli/credentials.rb