net-connector 0.17.1 → 0.17.2

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: 33503c92ccbe8f7000652c0b47b967a8f2f40e6c9edd7081ba305c3246efe820
4
- data.tar.gz: f2d4c434caab1b888d1b4cd9bf25a0def4e7e26eb63b2e6e5e93d8b56cc34c0d
3
+ metadata.gz: aadffbf2db023105e9b507fb732a89bd710207adf28456b2edb5184eb245fedf
4
+ data.tar.gz: 72cc71886fd9c6dce77171af3c8300f5f84af7c7e8192acd335e132c942b2b3c
5
5
  SHA512:
6
- metadata.gz: e8c126666632d1f4a708cbcc42c1df5ad434d37978a55fe2db363e3628f2c7e2779192d05ad7d8989933d389d92a6de520669a93a47c46209816a85d423abcca
7
- data.tar.gz: ad704438b1ed05942371fe3f57a7ebf66f563261406682013b18f57c8cfbc8b01a190a41a685a2991f4d53ff3e2912d2cf8c8e272b593bc83e1a439c821ef349
6
+ metadata.gz: 4d59d23ae05999d469f96837baf6b5f2eca3518104b8769947c36c860788627f3f9cb35c4e05151e364e51f159f0b171905f64fbfd30b963d096bf176cbf35d5
7
+ data.tar.gz: 0b0137d959ecc3be69fd72fbed2bcc562e95c615ab13a786c4485dcf107ff9de30376e2c731051edbac6f297b496be1cdc97c2ec321066c955c9a3a88ef369f6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # 更新记录
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.17.2 - 2026-10-23
6
+
7
+ - Migrate shared CLI entrypoints to ctx_cli 0.2.0 names without legacy aliases.
8
+ - Synchronize new directory ancestry across batch reports and local/SSH archives; synchronize uploaded-file cleanup.
9
+ - Keep host-key comparison and vendor parsing independent of host field separators.
10
+ - Reuse ctx_cli configuration, initialization, terminal and option boundaries without changing backup/TFTP/topology policies.
11
+
3
12
  ## 0.17.1 - 2026-10-22
4
13
 
5
14
  - Delegate shared configuration, private initialization, option groups and terminal interaction to ctx_cli 0.1.0.
@@ -159,7 +159,7 @@ module Net
159
159
  output, status = Open3.capture2e("ssh-keygen", "-f", path, "-F", @host)
160
160
  raise IOError, "reading device host key failed" unless [0, 1].include?(status.exitstatus)
161
161
 
162
- output.lines("\n").reject { |line| line.start_with?("#") }.map { |line| line.split[1, 2] }.uniq
162
+ output.lines("\n").reject { |line| line.start_with?("#") }.map { |line| line.split(" ")[1, 2] }.uniq
163
163
  end
164
164
 
165
165
  # 从指定快照中移除当前主机条目,不直接修改共享信任文件。
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
- require "ctx_cli/initialization"
4
+ require "ctx_cli/initializer"
5
5
  require "ctx_cli/terminal"
6
6
  require_relative "../settings"
7
7
  require_relative "../../config"
@@ -15,7 +15,7 @@ module Net
15
15
  # 绑定配置问答的输入输出,保留调用方对 IO 的所有权。
16
16
  def initialize(input: $stdin, output: $stderr)
17
17
  @input, @output = input, output
18
- @writer = CtxCli::Initialization.new(prefix: ".net-connector-config-")
18
+ @writer = CtxCli::Initializer.new(prefix: ".net-connector-config-")
19
19
  end
20
20
 
21
21
  # 完成默认值校验与可选问答后独占发布新配置,不覆盖已存在文件。
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../storage/directory"
4
+
3
5
  require "json"
4
6
  require_relative "../report"
5
7
  require "fileutils"
@@ -17,7 +19,7 @@ module Net
17
19
  # 以临时文件和重命名原子写入私有 JSON 报告。
18
20
  def write(report, directory:)
19
21
  destination = File.expand_path(directory)
20
- FileUtils.mkdir_p(destination, mode: 0o700)
22
+ Storage::Directory.prepare(destination)
21
23
  filename = "netdisco-#{report.mode}-#{report.started_at.strftime("%Y%m%dT%H%M%SZ")}-#{SecureRandom.hex(4)}.json"
22
24
  path = File.join(destination, filename)
23
25
  Storage::AtomicFile.write(path, JSON.pretty_generate(report.summary)).path
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../../storage/directory"
4
+
3
5
  require "digest"
4
6
  require "json"
5
7
  require "open3"
@@ -68,7 +70,7 @@ module Net
68
70
  end
69
71
  @password = password&.dup&.freeze
70
72
  @report_directory = File.expand_path(directory)
71
- FileUtils.mkdir_p(@report_directory, mode: 0o700)
73
+ Storage::Directory.prepare(@report_directory)
72
74
  @batch = "#{Time.now.getlocal("+08:00").strftime("%Y-%m-%d_%H-%M-%S")}-ssh-#{SecureRandom.hex(8)}"
73
75
  @launcher = launcher || method(:open_ssh)
74
76
  @custom_launcher = !launcher.nil?
@@ -179,7 +181,7 @@ module Net
179
181
  archive_path: remote_path(proof.fetch("archive_path")))
180
182
  outcome = outcome.with(backup: receipt)
181
183
  path = File.join(@report_directory, "tftp", archive_name)
182
- FileUtils.mkdir_p(File.dirname(path), mode: 0o700)
184
+ Storage::Directory.prepare(File.dirname(path))
183
185
  Storage::AtomicFile.write(path, bytes)
184
186
  outcome = outcome.with(backup: receipt.with(local_path: path))
185
187
  raise Tftp::Archive::Failure unless Storage::FileReader.fingerprint(path).sha256 == digest
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../storage/directory"
4
+
3
5
  require "fileutils"
4
6
  require "tempfile"
5
7
  require "digest"
@@ -50,7 +52,7 @@ module Net
50
52
  def initialize(directory:, root: nil)
51
53
  @report_directory = File.expand_path(directory)
52
54
  @server_root = root && File.realpath(root)
53
- FileUtils.mkdir_p(@report_directory, mode: 0o700)
55
+ Storage::Directory.prepare(@report_directory)
54
56
  @server_archive_directory = create_server_archive_directory if @server_root
55
57
  @remote_suffix = reserve_remote_suffix unless @server_root
56
58
  @verifier = Tftp::Verifier.new(root: @server_root)
@@ -113,7 +115,7 @@ module Net
113
115
  return unless bytes
114
116
 
115
117
  previous_directory = File.join(@server_archive_directory, "previous")
116
- FileUtils.mkdir_p(previous_directory, mode: 0o700)
118
+ Storage::Directory.prepare(previous_directory)
117
119
  archive_path = File.join(previous_directory, archive_filename(device, File.extname(remote_filename)))
118
120
  Storage::AtomicFile.write(archive_path, bytes)
119
121
  previous
@@ -126,7 +128,7 @@ module Net
126
128
  parent = File.join(@server_root, "archive")
127
129
  raise ArgumentError, "TFTP archive directory must not be a symlink" if File.symlink?(parent)
128
130
 
129
- FileUtils.mkdir_p(parent, mode: 0o700)
131
+ Storage::Directory.prepare(parent)
130
132
  raise ArgumentError, "TFTP archive directory escaped its root" unless File.realpath(parent) == parent
131
133
 
132
134
  base = batch_name
@@ -136,6 +138,7 @@ module Net
136
138
  path = File.join(parent, candidate)
137
139
  begin
138
140
  Dir.mkdir(path, 0o700)
141
+ Storage::Directory.sync(parent)
139
142
  return path
140
143
  rescue Errno::EEXIST
141
144
  number += 1
@@ -171,7 +174,7 @@ module Net
171
174
  receipt = outcome.backup
172
175
  saved_paths = { local_path: nil, archive_path: nil }
173
176
  batch_directory = File.join(@report_directory, "tftp")
174
- FileUtils.mkdir_p(batch_directory, mode: 0o700)
177
+ Storage::Directory.prepare(batch_directory)
175
178
  extension = File.extname(receipt.path)
176
179
  filename = archive_filename(outcome.device, extension)
177
180
  batch_archive_path = File.join(batch_directory, filename)
@@ -192,6 +195,7 @@ module Net
192
195
  raise Failure.new(host: outcome.device.host) unless Storage::FileReader.same_entry?(uploaded_path, uploaded_fingerprint)
193
196
 
194
197
  File.unlink(uploaded_path)
198
+ Storage::Directory.sync(File.dirname(uploaded_path))
195
199
  end
196
200
  outcome.with(backup: receipt.with(**saved_paths))
197
201
  rescue StandardError => error
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
3
+ require_relative "directory"
4
4
 
5
5
  module Net
6
6
  module Connector
@@ -9,7 +9,7 @@ module Net
9
9
  class BatchDirectory
10
10
  # 固定 UTC+8,不依赖运行主机时区;mkdir 原子防止并发任务覆盖。
11
11
  def self.create(root, time: Time.now)
12
- FileUtils.mkdir_p(root, mode: 0o700)
12
+ Directory.prepare(root)
13
13
  stamp = time.getlocal("+08:00").strftime("%Y-%m-%d_%H-%M-%S")
14
14
  suffix = 0
15
15
  loop do
@@ -17,6 +17,7 @@ module Net
17
17
  path = File.join(root, name)
18
18
  begin
19
19
  Dir.mkdir(path, 0o700)
20
+ Directory.sync(root)
20
21
  return path
21
22
  rescue Errno::EEXIST
22
23
  suffix += 1
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Net
6
+ module Connector
7
+ module Storage
8
+ # 新建目录与已有目录的文件发布共用持久化边界。
9
+ module Directory
10
+ def self.prepare(path)
11
+ path = File.expand_path(path)
12
+ missing, parent = [], path
13
+ until File.directory?(parent)
14
+ missing << parent
15
+ parent = File.dirname(parent)
16
+ end
17
+ FileUtils.mkdir_p(path, mode: 0o700)
18
+ missing.reverse_each { |created| sync(File.dirname(created)) }
19
+ path
20
+ end
21
+
22
+ def self.sync(path)
23
+ File.open(File.realpath(path), File::RDONLY | File::NOFOLLOW) { |directory| directory.fsync }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,6 +4,7 @@ module Net
4
4
  module Connector
5
5
  # 文件存储不依赖设备连接;备份、离线导出和批量报告共享这些原语。
6
6
  module Storage
7
+ autoload :Directory, File.expand_path("storage/directory", __dir__)
7
8
  autoload :BatchDirectory, File.expand_path("storage/batch_directory", __dir__)
8
9
  autoload :BackupLock, File.expand_path("storage/backup_lock", __dir__)
9
10
  autoload :AtomicFile, File.expand_path("storage/atomic_file", __dir__)
@@ -43,7 +43,7 @@ module Net
43
43
  local_column = output.match?(/^[ \t]*System Name[ \t]+Local Interface\b/i) ? 1 : 0
44
44
  lines = neighbor_lines(output)
45
45
  return unless lines.all? do |line|
46
- interface = line.split.fetch(local_column, nil)
46
+ interface = line.split(" ").fetch(local_column, nil)
47
47
  interface&.match?(/\A(?:[A-Za-z][A-Za-z-]*\d+(?:\/\d+)+|(?:Bridge-Aggregation|BAGG|Route-Aggregation|RAGG)\d+)(?:[:.]\d+)*\z/i)
48
48
  end
49
49
 
@@ -32,7 +32,7 @@ module Net
32
32
 
33
33
  # 从山石成功回显提取实际目标文件名。
34
34
  def remote_path(_target, result)
35
- filename = completion_line(result)&.split&.last
35
+ filename = completion_line(result)&.split(" ")&.last
36
36
  TftpTarget.validate_path!(filename)
37
37
  end
38
38
 
@@ -3,6 +3,6 @@
3
3
  module Net
4
4
  module Connector
5
5
  # gemspec、CLI 与发布工具共用此版本,运行时读取不触发设备依赖加载。
6
- VERSION = "0.17.1"
6
+ VERSION = "0.17.2"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-connector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.1
4
+ version: 0.17.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - net-connector contributors
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.0
18
+ version: 0.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.0
25
+ version: 0.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: expect-pty
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -491,6 +491,7 @@ files:
491
491
  - lib/net/connector/storage/atomic_file.rb
492
492
  - lib/net/connector/storage/backup_lock.rb
493
493
  - lib/net/connector/storage/batch_directory.rb
494
+ - lib/net/connector/storage/directory.rb
494
495
  - lib/net/connector/storage/file_reader.rb
495
496
  - lib/net/connector/storage/saved_config.rb
496
497
  - lib/net/connector/textfsm.rb