vxlan 0.5.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cd74f1778008071770a76681129f64d7fc8d803ca27336fbac450147e33cd01
4
- data.tar.gz: d4ba8a592e430b4e8c2a81e2218fe65f70c617d5fce7f60744d893a734a80b53
3
+ metadata.gz: c114dcb0645a2b10176e8a6cc68cdea2b517edd069c166d20b02046dddaf766a
4
+ data.tar.gz: edccf86d53afb7ad825c2fd468dcaba21ebda26a7b2330233fc2461840ba220f
5
5
  SHA512:
6
- metadata.gz: e45e95ed6294d0f1ae0014d60ef13b1f8fad1f60a5832815b46966e1159d35b63159700410afe0f1191cb05155806b0ee5ad73050f27019e76646dfc462a5d63
7
- data.tar.gz: 84d292f0dc18f67f949a73cd545293556d5e24722d995a9c161eb5a2889a89bc5cbf4afce379da9660c8bf803741a74b6b5e1ec63d47e9adac2d6214bfc65cfc
6
+ metadata.gz: 34c6082ad93884802d028f56cb2821cb8ae057537ba4618d7dab6e9edb3d73d27dfaebf8a6e99e502f334aecd9617e5aacc0c579070121e7c2364a5a78e3c185
7
+ data.tar.gz: 51864b6ddf301c26c41f3eb3fb77b46a2baa32d8047f0f806595a839ef20e32e976c022843bc7bf208794ec54ac12ed31ee2b158f9647cf1e824c191b5bd9fb1
data/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ Changes to the public contract are recorded here. Versions follow Semantic Versi
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.6.0 (2026-10-29)
8
+
9
+ - Require RuntimeParser platform at construction and add parse_many. Rename Snapshot Ruby members to observations/observation while retaining dimensions in persisted reports. Use Catalog and shared Credentials in CLI; reject selected duplicate addresses across platforms before connecting.
10
+
7
11
  ## 0.5.1 (2026-10-24)
8
12
 
9
13
  - Validate EVPN configured-peer totals within each VRF and address-family block, retain administrative shutdown and unknown AS identities, and preserve state-valued prefix totals.
data/README-zh.md CHANGED
@@ -45,7 +45,7 @@ output = <<~CLI
45
45
  Neighbor ID Pri State Up Time Address Interface
46
46
  192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
47
47
  CLI
48
- observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
48
+ observation = Vxlan::RuntimeParser.new(platform: :cisco_nxos).parse(:ospf_neighbors, output, complete: true)
49
49
  puts "OSPF parse status: #{observation.status}"
50
50
  puts "Neighbors: #{observation.records.size}"
51
51
  observation.records.each { |record| p record }
@@ -136,3 +136,10 @@ MIT,见 [LICENSE](LICENSE)。
136
136
  `vxlan collect` 默认输出逐设备诊断,使用 `--scripts` 可选导出配置建议;`vxlan review --batch DIR` 可离线补生成既有报告的候选配置、条件回退和只读验收命令。导出不会执行变更;独立 `apply --plan FILE --item ID` 必须在真实终端明确确认,配置漂移或失败即停止。详见[修复建议说明](docs/remediation.md)。 业务分层、会话所有权与执行边界见[架构说明](docs/architecture-zh.md)。
137
137
 
138
138
  报告阅读顺序与 net-connector topology 一致:`summary.txt` → `devices/*.txt` → `scripts/*.cfg`。不生成 Markdown 报告。所有可执行增量汇总于带 SHA256 校验的 `plan.json`,通过 `vxlan apply --plan plan.json --item ID` 选择一项并交互确认。
139
+
140
+
141
+ ## API 迁移
142
+
143
+ `RuntimeParser.new(platform:, model: nil)` 固定解析平台;`parse(name, text, complete: false, command: nil)` 与 `parse_many(names, text, complete: false, command: nil)` 返回领域 Observation。`Snapshot.new(observations: ...)`、`#observations`、`#observation(name)` 是统一 Ruby API,旧 `dimensions` 成员及 Wireless `dimension` 入口已移除。已知未请求维度返回 `not_requested`,未知名称抛出 `ArgumentError`;持久化 JSON 仍保留 `dimensions` 字段。
144
+
145
+ 动态清单使用 `Netdisco::Catalog`;选中地址的任何重复记录(包括另一平台)都会阻止连接。范围外冲突只输出安全诊断。平台 `NC_<PLATFORM>_ENABLE_PASSWORD` 优先于 `NC_ENABLE_PASSWORD`。
data/README.md CHANGED
@@ -45,7 +45,7 @@ output = <<~CLI
45
45
  Neighbor ID Pri State Up Time Address Interface
46
46
  192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
47
47
  CLI
48
- observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
48
+ observation = Vxlan::RuntimeParser.new(platform: :cisco_nxos).parse(:ospf_neighbors, output, complete: true)
49
49
  puts "OSPF parse status: #{observation.status}"
50
50
  puts "Neighbors: #{observation.records.size}"
51
51
  observation.records.each { |record| p record }
@@ -142,3 +142,10 @@ Review output follows the net-connector topology layout: `summary.txt`, `summary
142
142
  Apply returns `2` on failure and `130` on interruption even if stderr cannot accept diagnostics. Existing execution journals retain uncertain device effects; commands are never replayed automatically. Unexpected positional arguments are rejected, including after `--help`.
143
143
 
144
144
  Offline review requires `snapshot.target.host` to match the canonical device host. Reports without target identity must be collected again; the unbound legacy format is no longer accepted.
145
+
146
+
147
+ ## API migration
148
+
149
+ `RuntimeParser.new(platform:, model: nil)` fixes the platform for `parse(name, text, complete: false, command: nil)` and `parse_many(names, text, complete: false, command: nil)`. Snapshots use `observations:` and expose `observations` / `observation(name)`; the old Ruby `dimensions` member and Wireless `dimension` accessor are removed. Known missing dimensions return `not_requested`; unknown names raise `ArgumentError`. Persisted JSON keeps its `dimensions` field.
150
+
151
+ Dynamic inventory uses `Netdisco::Catalog`. Any duplicate row for a selected address, including another platform, prevents connection; excluded conflicts retain safe diagnostics. `NC_<PLATFORM>_ENABLE_PASSWORD` takes precedence over `NC_ENABLE_PASSWORD`.
data/examples/offline.rb CHANGED
@@ -9,7 +9,7 @@ output = <<~CLI
9
9
  Neighbor ID Pri State Up Time Address Interface
10
10
  192.0.2.2 1 FULL/ - 1d 192.0.2.3 Eth1/1
11
11
  CLI
12
- observation = Vxlan::RuntimeParser.new.parse(:ospf_neighbors, output, complete: true)
12
+ observation = Vxlan::RuntimeParser.new(platform: :cisco_nxos).parse(:ospf_neighbors, output, complete: true)
13
13
  puts "OSPF parse status: #{observation.status}"
14
14
  puts "Neighbors: #{observation.records.size}"
15
15
  observation.records.each { |record| p record }
@@ -34,17 +34,27 @@ module Vxlan
34
34
  def load_network
35
35
  require "net/connector"
36
36
  rescue LoadError
37
- raise LoadError, "Online collection requires net-connector >= 0.18.0; add it to the host Gemfile or run gem install net-connector", cause: nil
37
+ raise LoadError, "Online collection requires net-connector >= 0.19.0; add it to the host Gemfile or run gem install net-connector", cause: nil
38
38
  end
39
39
 
40
- # YAML 静态设备或显式 Netdisco 来源二选一;所有重复地址在连接前拒绝。
40
+ # YAML 静态设备或显式 Netdisco 来源二选一;选中地址的所有冲突记录在连接前拒绝。
41
41
  def inventory(options, progress:)
42
42
  values = options.values
43
+ selection = Net::Connector::Inventory::Filters.selection(values)
43
44
  rows = if options.dynamic_inventory?
44
45
  progress.reading_inventory
45
46
  settings = options.netdisco_settings
46
- Net::Connector::Netdisco::Fleet.new(settings: settings).devices.filter_map do |device|
47
- next unless device.ready? && device.vendor == :cisco_nxos
47
+ policy = settings.snapshot(mode: :inventory)
48
+ devices = Net::Connector::Netdisco::Catalog.new(settings: settings).devices(policy: policy)
49
+ devices.filter_map(&:issue).tally.each { |code, count| progress.notice("Inventory: #{code}=#{count}") }
50
+ devices.filter_map do |device|
51
+ next unless device.vendor == :cisco_nxos
52
+ platform = "cisco_nxos"
53
+ if device.issue == :duplicate_host && policy.rules.selected?(device.host, device.vendor, name: device.name, model: device.model) &&
54
+ selection.selected?(device.host, platform, name: device.name || device.host, model: device.model)
55
+ raise CtxCli::Collection::Error, "Duplicate management address in selected inventory"
56
+ end
57
+ next unless device.ready?
48
58
  { "host" => device.host, "name" => device.name || device.host, "model" => device.model }
49
59
  end
50
60
  else
@@ -52,7 +62,6 @@ module Vxlan
52
62
  end
53
63
  targets = options.normalize_devices(rows)
54
64
  total = targets.size
55
- selection = Net::Connector::Inventory::Filters.selection(values)
56
65
  selected = targets.select do |device|
57
66
  selection.selected?(device.fetch("host"), "cisco_nxos", name: device["name"], model: device["model"])
58
67
  end
@@ -86,16 +95,10 @@ module Vxlan
86
95
 
87
96
  # 缺失用户名时不尝试连接;密码允许省略以支持明确的 SSH key/agent。
88
97
  def credentials!(options)
89
- username = secret(options, "NC_CISCO_NXOS_USERNAME") || secret(options, "NC_DEVICE_USERNAME")
90
- raise CtxCli::Collection::Error, "Set NC_DEVICE_USERNAME or NC_CISCO_NXOS_USERNAME; provide a password through the environment or use an SSH key/agent" unless username
91
- options.values.fetch("ssh").transform_keys(&:to_sym).merge(username: username,
92
- password: secret(options, "NC_CISCO_NXOS_PASSWORD") || secret(options, "NC_DEVICE_PASSWORD"), enable_password: secret(options, "NC_ENABLE_PASSWORD"))
93
- end
94
-
95
- # 空白环境值视为缺失,不修改或回显凭据。
96
- def secret(options, name)
97
- value = options.environment[name]
98
- value unless value.nil? || value.strip.empty?
98
+ require "net/connector/credentials"
99
+ credentials = Net::Connector::Credentials.new(env: options.environment).resolve(platform: :cisco_nxos)
100
+ raise CtxCli::Collection::Error, "Set NC_DEVICE_USERNAME or platform NC_*_USERNAME; provide a password through the environment or use an SSH key/agent" unless credentials
101
+ options.values.fetch("ssh").transform_keys(&:to_sym).merge(credentials)
99
102
  end
100
103
 
101
104
  # 每个任务使用独立 Collector 和会话;公共执行器负责有界并发和线程回收。
@@ -114,7 +117,7 @@ module Vxlan
114
117
  snapshot
115
118
  end
116
119
 
117
- private :load_network, :credentials!, :secret
120
+ private :load_network, :credentials!
118
121
  end
119
122
  end
120
123
  end
@@ -6,7 +6,7 @@ require "optparse"
6
6
  require "ctx_cli/options"
7
7
  require "ctx_cli/terminal"
8
8
  require "ctx_cli/initializer"
9
- gem "net-connector", "~> 0.18.0"
9
+ gem "net-connector", "~> 0.19.0"
10
10
  require "net/connector/inventory/filters"
11
11
  require "yaml"
12
12
  require "ipaddr"
@@ -67,7 +67,7 @@ module Vxlan
67
67
  @environment = @files.environment
68
68
  @config, @env_file = @files.config, @files.env_file
69
69
  rescue LoadError
70
- raise LoadError, "Collection configuration requires net-connector >= 0.18.0; reading .env also requires dotenv", cause: nil
70
+ raise LoadError, "Collection configuration requires net-connector >= 0.19.0; reading .env also requires dotenv", cause: nil
71
71
  end
72
72
 
73
73
  # 公共与专用文件先合并,进程值和 CLI 最后覆盖;数组不追加设备。
@@ -37,7 +37,7 @@ module Vxlan
37
37
  observation = if options[:dimension] == :configuration
38
38
  Vxlan::Config.new(model: options[:model]).parse(body, complete: options[:complete])
39
39
  else
40
- Vxlan::RuntimeParser.new(model: options[:model]).parse(options[:dimension], body, complete: options[:complete])
40
+ Vxlan::RuntimeParser.new(platform: :cisco_nxos, model: options[:model]).parse(options[:dimension], body, complete: options[:complete])
41
41
  end
42
42
  if options[:json]
43
43
  @output.json(observation.to_h)
@@ -85,7 +85,7 @@ module Vxlan
85
85
  :failed
86
86
  end
87
87
  observations[:collection] ||= Observation.new(name: :collection, status: status, completed_at: @clock.call)
88
- Snapshot.new(target: target, fabric_key: fabric_key, dimensions: observations, observed_at: @clock.call)
88
+ Snapshot.new(target: target, fabric_key: fabric_key, observations: observations, observed_at: @clock.call)
89
89
  end
90
90
 
91
91
  # 采集设备回显前,禁用分页并归一化终端宽度。
@@ -7,16 +7,24 @@ module Vxlan
7
7
  # 将纯平台解析事实封装为对应 VXLAN 维度观测。
8
8
  class RuntimeParser
9
9
  # 按请求的平台和型号选择运行解析行为。
10
- def initialize(platform: :cisco_nxos, model: nil)
10
+ def initialize(platform:, model: nil)
11
11
  @profile = ParserRegistry.fetch(platform, model: model)
12
12
  end
13
13
 
14
14
  # 拒绝配置维度,仅解析所选 VXLAN 运行查询。
15
- def parse(name, text, complete: false)
16
- raise ArgumentError, "Unsupported runtime dimension" if name == :configuration || !@profile.commands.key?(name)
15
+ def parse(name, text, complete: false, command: nil)
16
+ parse_many([ name ], text, complete: complete, command: command).fetch(name)
17
+ end
17
18
 
18
- result = @profile.parse(name, text, complete: complete)
19
- Observation.new(name: name, **result.to_h)
19
+ # 单条正文只允许投影属于同一声明命令的运行维度。
20
+ def parse_many(names, text, complete: false, command: nil)
21
+ unless names.is_a?(Array) && names.any? && names.uniq == names &&
22
+ names.all? { |name| name.is_a?(Symbol) && name != :configuration && @profile.commands.key?(name) }
23
+ raise ArgumentError, "Unsupported runtime dimensions"
24
+ end
25
+ @profile.parse_many(names, text, complete: complete, command: command).to_h do |name, result|
26
+ [ name, Observation.new(name: name, **result.to_h) ]
27
+ end
20
28
  end
21
29
  end
22
30
  end
@@ -2,23 +2,35 @@
2
2
 
3
3
  require "ctx_parser/immutable"
4
4
  require_relative "observation"
5
+ require_relative "parser_registry"
5
6
 
6
7
  module Vxlan
7
8
  # 携带单设备 Fabric 身份及观测结果,不管理资产清单。
8
- Snapshot = Data.define(:target, :fabric_key, :dimensions, :observed_at) do
9
+ Snapshot = Data.define(:target, :fabric_key, :observations, :observed_at) do
9
10
  # 事实由不可变领域观测持有;仅复制维度选择,不重复复制事实。
10
- def initialize(target:, fabric_key:, dimensions:, observed_at:)
11
- unless dimensions.is_a?(Hash) && dimensions.all? { |name, value| value.is_a?(Observation) && value.name == name }
11
+ def initialize(target:, fabric_key:, observations:, observed_at:)
12
+ unless observations.is_a?(Hash) && observations.all? { |name, value| value.is_a?(Observation) && value.name == name }
12
13
  raise ArgumentError, "Dimensions must match their observations"
13
14
  end
14
15
 
15
16
  super(target: CtxParser::Immutable.copy(target), fabric_key: CtxParser::Immutable.copy(fabric_key),
16
- dimensions: dimensions.to_h { |name, value| [ name, value ] }.freeze, observed_at: observed_at&.dup&.freeze)
17
+ observations: observations.to_h { |name, value| [ name, value ] }.freeze, observed_at: observed_at&.dup&.freeze)
17
18
  end
18
19
 
20
+ # 未请求的已知维度保留明确状态;未知维度属于调用错误。
21
+ def observation(name)
22
+ unless name == :collection || CiscoNxos::Profile::COMMANDS.key?(name)
23
+ raise ArgumentError, "Unknown VXLAN dimension"
24
+ end
25
+ observations.fetch(name) { Observation.new(name: name, status: :not_requested) }
26
+ end
27
+
28
+ # 报告格式独立于 Ruby 成员命名,已有离线证据继续使用 dimensions。
29
+ def to_h = super.except(:observations).merge(dimensions: observations)
30
+
19
31
  # 判断所有已选事实是否都有完整证据支持。
20
32
  def complete?
21
- selected = dimensions.values.reject { |dimension| dimension.status == :not_requested }
33
+ selected = observations.values.reject { |dimension| dimension.status == :not_requested }
22
34
  selected.any? && selected.all?(&:complete?)
23
35
  end
24
36
 
data/lib/vxlan/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Vxlan
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.5.1"
5
+ VERSION = "0.6.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vxlan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors
@@ -29,42 +29,60 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '0'
32
+ version: 0.3.0
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '0.4'
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
36
39
  requirements:
37
40
  - - ">="
38
41
  - !ruby/object:Gem::Version
39
- version: '0'
42
+ version: 0.3.0
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '0.4'
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: ctx_parser
42
48
  requirement: !ruby/object:Gem::Requirement
43
49
  requirements:
44
50
  - - ">="
45
51
  - !ruby/object:Gem::Version
46
- version: '0'
52
+ version: 0.3.0
53
+ - - "<"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.4'
47
56
  type: :runtime
48
57
  prerelease: false
49
58
  version_requirements: !ruby/object:Gem::Requirement
50
59
  requirements:
51
60
  - - ">="
52
61
  - !ruby/object:Gem::Version
53
- version: '0'
62
+ version: 0.3.0
63
+ - - "<"
64
+ - !ruby/object:Gem::Version
65
+ version: '0.4'
54
66
  - !ruby/object:Gem::Dependency
55
67
  name: ctx_driver
56
68
  requirement: !ruby/object:Gem::Requirement
57
69
  requirements:
58
70
  - - ">="
59
71
  - !ruby/object:Gem::Version
60
- version: '0'
72
+ version: 0.3.0
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
61
76
  type: :runtime
62
77
  prerelease: false
63
78
  version_requirements: !ruby/object:Gem::Requirement
64
79
  requirements:
65
80
  - - ">="
66
81
  - !ruby/object:Gem::Version
67
- version: '0'
82
+ version: 0.3.0
83
+ - - "<"
84
+ - !ruby/object:Gem::Version
85
+ version: '0.4'
68
86
  - !ruby/object:Gem::Dependency
69
87
  name: digest
70
88
  requirement: !ruby/object:Gem::Requirement