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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/net/connector/engine/known_hosts.rb +1 -1
- data/lib/net/connector/netdisco/config/initializer.rb +2 -2
- data/lib/net/connector/netdisco/report/store.rb +3 -1
- data/lib/net/connector/netdisco/ssh/archive/client.rb +4 -2
- data/lib/net/connector/netdisco/tftp/archive.rb +8 -4
- data/lib/net/connector/storage/batch_directory.rb +3 -2
- data/lib/net/connector/storage/directory.rb +28 -0
- data/lib/net/connector/storage.rb +1 -0
- data/lib/net/connector/vendor/h3c/topology.rb +1 -1
- data/lib/net/connector/vendor/hillstone/tftp.rb +1 -1
- data/lib/net/connector/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aadffbf2db023105e9b507fb732a89bd710207adf28456b2edb5184eb245fedf
|
|
4
|
+
data.tar.gz: 72cc71886fd9c6dce77171af3c8300f5f84af7c7e8192acd335e132c942b2b3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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::
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|