net-connector 0.4.0 → 0.4.1
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 +73 -65
- data/README.md +88 -95
- data/docs/VERIFICATION.md +2 -1
- data/docs/architecture.md +144 -248
- data/lib/net/connector/device/base.rb +1 -0
- data/lib/net/connector/device/interface_description.rb +3 -0
- data/lib/net/connector/device/profile/builder.rb +236 -0
- data/lib/net/connector/device/profile.rb +2 -227
- data/lib/net/connector/device/running_config/strategy.rb +5 -0
- data/lib/net/connector/device/running_config.rb +5 -0
- data/lib/net/connector/engine/dialogue.rb +1 -0
- data/lib/net/connector/engine/errors.rb +2 -0
- data/lib/net/connector/engine/log.rb +13 -9
- data/lib/net/connector/engine/session.rb +1 -0
- data/lib/net/connector/netdisco/cli.rb +20 -20
- data/lib/net/connector/netdisco/device.rb +4 -8
- data/lib/net/connector/netdisco/fleet.rb +2 -48
- data/lib/net/connector/netdisco/plan.rb +77 -0
- data/lib/net/connector/netdisco/planner.rb +12 -23
- data/lib/net/connector/netdisco/worker.rb +31 -24
- data/lib/net/connector/operations/tftp/file_upload.rb +42 -0
- data/lib/net/connector/operations/tftp/strategy.rb +9 -11
- data/lib/net/connector/operations/topology/strategy.rb +14 -0
- data/lib/net/connector/operations/topology.rb +1 -0
- data/lib/net/connector/vendor/cisco_ios/running_config.rb +1 -0
- data/lib/net/connector/vendor/cisco_ios/topology.rb +9 -0
- data/lib/net/connector/vendor/cisco_nxos/running_config.rb +1 -0
- data/lib/net/connector/vendor/h3c/tftp_backup.rb +2 -18
- data/lib/net/connector/vendor/h3c/topology.rb +9 -0
- data/lib/net/connector/vendor/hillstone/running_config.rb +1 -0
- data/lib/net/connector/vendor/hillstone/tftp_backup.rb +4 -0
- data/lib/net/connector/vendor/hillstone/topology.rb +9 -0
- data/lib/net/connector/vendor/huawei/tftp_backup.rb +2 -18
- data/lib/net/connector/vendor/palo_alto/running_config.rb +4 -0
- data/lib/net/connector/vendor/palo_alto/tftp_backup.rb +7 -5
- data/lib/net/connector/vendor/palo_alto/topology.rb +10 -0
- data/lib/net/connector/vendor/radware/tftp_backup.rb +2 -2
- data/lib/net/connector/vendor/radware/topology.rb +2 -0
- data/lib/net/connector/version.rb +1 -1
- metadata +26 -24
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module Connector
|
|
5
|
+
class Profile
|
|
6
|
+
# 构造器把声明块中的领域语言转换为设备档案字段。
|
|
7
|
+
class Builder
|
|
8
|
+
# 保存继承值并提供 commands、prompts 等有限的声明入口。
|
|
9
|
+
def initialize(parent)
|
|
10
|
+
@values = parent.to_h
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 声明运行配置和保存配置命令。
|
|
14
|
+
def commands(&definition)
|
|
15
|
+
check_block!(:commands, definition)
|
|
16
|
+
commands = Commands.new(
|
|
17
|
+
@values[:config_commands], @values[:save_commands]
|
|
18
|
+
)
|
|
19
|
+
commands.instance_eval(&definition)
|
|
20
|
+
@values[:config_commands] = commands.config_commands
|
|
21
|
+
@values[:save_commands] = commands.save_commands
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 声明登录、命令、凭据和提权提示符。
|
|
26
|
+
def prompts(&definition)
|
|
27
|
+
check_block!(:prompts, definition)
|
|
28
|
+
Prompts.new(@values).instance_eval(&definition)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 声明分页模式及自动翻页响应。
|
|
33
|
+
def pager(&definition)
|
|
34
|
+
check_block!(:pager, definition)
|
|
35
|
+
Pager.new(@values).instance_eval(&definition)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 声明认证失败和命令失败的输出模式。
|
|
40
|
+
def errors(&definition)
|
|
41
|
+
check_block!(:errors, definition)
|
|
42
|
+
Errors.new(@values).instance_eval(&definition)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 声明登录挑战和命令确认交互。
|
|
47
|
+
def interactions(&definition)
|
|
48
|
+
check_block!(:interactions, definition)
|
|
49
|
+
interactions = Interactions.new(@values)
|
|
50
|
+
interactions.instance_eval(&definition)
|
|
51
|
+
@values[:login_interactions] = interactions.login_interactions
|
|
52
|
+
@values[:confirmation_interactions] = interactions.confirmation_interactions
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 声明进入特权模式所需的命令和提示符。
|
|
57
|
+
def privilege(&definition)
|
|
58
|
+
check_block!(:privilege, definition)
|
|
59
|
+
Privilege.new(@values).instance_eval(&definition)
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 声明旧版 SSH 协商参数。
|
|
64
|
+
def ssh(&definition)
|
|
65
|
+
check_block!(:ssh, definition)
|
|
66
|
+
Ssh.new(@values).instance_eval(&definition)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# 设置设备命令的默认超时秒数。
|
|
71
|
+
def command_timeout(seconds)
|
|
72
|
+
@values[:command_timeout] = seconds
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# 设置避免设备自动换行的终端宽高。
|
|
77
|
+
def terminal_size(width, height = nil)
|
|
78
|
+
@values[:terminal_size] = height.nil? ? width : [width, height]
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 绑定已加载的策略类;nil 明确关闭继承的业务能力。
|
|
83
|
+
def tftp_strategy(klass)
|
|
84
|
+
@values[:tftp_strategy] = klass
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# 指定运行配置采集策略,覆盖继承的厂商规则。
|
|
89
|
+
def running_config_strategy(klass)
|
|
90
|
+
@values[:running_config_strategy] = klass
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 指定邻居发现和描述下发策略,nil 表示关闭能力。
|
|
95
|
+
def topology_strategy(klass)
|
|
96
|
+
@values[:topology_strategy] = klass
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 将声明字段校验并构造成不可变档案。
|
|
101
|
+
def build = Profile.new(**@values)
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# 块式声明入口必须有明确的子 DSL,避免静默忽略配置。
|
|
106
|
+
def check_block!(name, definition)
|
|
107
|
+
raise ArgumentError, "#{name} requires a block" unless definition
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# 命令集合的声明对象。
|
|
111
|
+
class Commands
|
|
112
|
+
attr_reader :config_commands, :save_commands
|
|
113
|
+
|
|
114
|
+
# 复制继承的命令,后续修改不会污染父类档案。
|
|
115
|
+
def initialize(config_commands, save_commands)
|
|
116
|
+
@config_commands = config_commands&.dup
|
|
117
|
+
@save_commands = save_commands.dup
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 声明读取运行配置的命令序列。
|
|
121
|
+
def running_config(*commands)
|
|
122
|
+
@config_commands = commands.flatten
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# 声明保存配置的命令序列。
|
|
126
|
+
def save_config(*commands)
|
|
127
|
+
@save_commands = commands.flatten
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# 提示符的声明对象。
|
|
132
|
+
class Prompts
|
|
133
|
+
# 将登录、命令、用户名、密码和特权提示写入当前档案。
|
|
134
|
+
def initialize(values)
|
|
135
|
+
@values = values
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# 声明登录结束提示符。
|
|
139
|
+
def login(pattern) = @values[:login_prompt] = pattern
|
|
140
|
+
|
|
141
|
+
# 声明普通命令结束提示符。
|
|
142
|
+
def command(pattern) = @values[:command_prompt] = pattern
|
|
143
|
+
|
|
144
|
+
# 声明用户名输入提示。
|
|
145
|
+
def username(pattern) = @values[:username_prompt] = pattern
|
|
146
|
+
|
|
147
|
+
# 声明密码输入提示。
|
|
148
|
+
def password(pattern) = @values[:password_prompt] = pattern
|
|
149
|
+
|
|
150
|
+
# 声明特权模式提示符。
|
|
151
|
+
def privilege(pattern) = @values[:privilege_prompt] = pattern
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# 分页规则的声明对象。
|
|
155
|
+
class Pager
|
|
156
|
+
# 将分页模式和响应写入当前档案。
|
|
157
|
+
def initialize(values)
|
|
158
|
+
@values = values
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# 声明分页提示匹配模式。
|
|
162
|
+
def pattern(value) = @values[:pager_pattern] = value
|
|
163
|
+
|
|
164
|
+
# 声明匹配分页提示后发送的字节。
|
|
165
|
+
def response(value) = @values[:pager_response] = value
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# 设备错误模式的声明对象。
|
|
169
|
+
class Errors
|
|
170
|
+
# 将错误模式列表写入当前档案。
|
|
171
|
+
def initialize(values)
|
|
172
|
+
@values = values
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# 替换认证失败模式列表。
|
|
176
|
+
def authentication(*patterns)
|
|
177
|
+
@values[:authentication_error_patterns] = patterns.flatten
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# 替换命令失败模式列表。
|
|
181
|
+
def command(*patterns)
|
|
182
|
+
@values[:command_error_patterns] = patterns.flatten
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# 登录和命令交互的声明对象。
|
|
187
|
+
class Interactions
|
|
188
|
+
attr_reader :login_interactions, :confirmation_interactions
|
|
189
|
+
|
|
190
|
+
# 复制继承的交互规则,子类可以在其上追加设备差异。
|
|
191
|
+
def initialize(values)
|
|
192
|
+
@login_interactions = values[:login_interactions].dup
|
|
193
|
+
@confirmation_interactions = values[:confirmation_interactions].dup
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# 声明登录阶段的一次性应答。
|
|
197
|
+
def login(pattern, response:, sensitive: false, limit: 1, capture: true)
|
|
198
|
+
@login_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
199
|
+
limit: limit, capture: capture)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# 声明命令阶段的确认应答。
|
|
203
|
+
def confirm(pattern, response:, sensitive: false, limit: nil, capture: true)
|
|
204
|
+
@confirmation_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
205
|
+
limit: limit, capture: capture)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# 特权认证规则的声明对象。
|
|
210
|
+
class Privilege
|
|
211
|
+
# 将特权命令和结束提示写入当前档案。
|
|
212
|
+
def initialize(values)
|
|
213
|
+
@values = values
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# 声明进入特权模式的设备命令。
|
|
217
|
+
def command(value) = @values[:privilege_command] = value
|
|
218
|
+
|
|
219
|
+
# 声明特权命令完成后的提示符。
|
|
220
|
+
def prompt(value) = @values[:privilege_prompt] = value
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# SSH 兼容参数的声明对象。
|
|
224
|
+
class Ssh
|
|
225
|
+
# 将旧版 SSH 参数写入当前档案。
|
|
226
|
+
def initialize(values)
|
|
227
|
+
@values = values
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# 声明连接旧版设备所需的参数序列。
|
|
231
|
+
def legacy_arguments(*arguments) = @values[:legacy_ssh_arguments] = arguments.flatten
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -110,233 +110,6 @@ module Net
|
|
|
110
110
|
"save_commands=#{save_commands.size}>"
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# 构造器把声明块中的领域语言转换为设备档案字段。
|
|
114
|
-
class Builder
|
|
115
|
-
# 保存继承值并提供 commands、prompts 等有限的声明入口。
|
|
116
|
-
def initialize(parent)
|
|
117
|
-
@values = parent.to_h
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# 声明运行配置和保存配置命令。
|
|
121
|
-
def commands(&definition)
|
|
122
|
-
check_block!(:commands, definition)
|
|
123
|
-
commands = Commands.new(
|
|
124
|
-
@values[:config_commands], @values[:save_commands]
|
|
125
|
-
)
|
|
126
|
-
commands.instance_eval(&definition)
|
|
127
|
-
@values[:config_commands] = commands.config_commands
|
|
128
|
-
@values[:save_commands] = commands.save_commands
|
|
129
|
-
self
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# 声明登录、命令、凭据和提权提示符。
|
|
133
|
-
def prompts(&definition)
|
|
134
|
-
check_block!(:prompts, definition)
|
|
135
|
-
Prompts.new(@values).instance_eval(&definition)
|
|
136
|
-
self
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# 声明分页模式及自动翻页响应。
|
|
140
|
-
def pager(&definition)
|
|
141
|
-
check_block!(:pager, definition)
|
|
142
|
-
Pager.new(@values).instance_eval(&definition)
|
|
143
|
-
self
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# 声明认证失败和命令失败的输出模式。
|
|
147
|
-
def errors(&definition)
|
|
148
|
-
check_block!(:errors, definition)
|
|
149
|
-
Errors.new(@values).instance_eval(&definition)
|
|
150
|
-
self
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# 声明登录挑战和命令确认交互。
|
|
154
|
-
def interactions(&definition)
|
|
155
|
-
check_block!(:interactions, definition)
|
|
156
|
-
interactions = Interactions.new(@values)
|
|
157
|
-
interactions.instance_eval(&definition)
|
|
158
|
-
@values[:login_interactions] = interactions.login_interactions
|
|
159
|
-
@values[:confirmation_interactions] = interactions.confirmation_interactions
|
|
160
|
-
self
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# 声明进入特权模式所需的命令和提示符。
|
|
164
|
-
def privilege(&definition)
|
|
165
|
-
check_block!(:privilege, definition)
|
|
166
|
-
Privilege.new(@values).instance_eval(&definition)
|
|
167
|
-
self
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# 声明旧版 SSH 协商参数。
|
|
171
|
-
def ssh(&definition)
|
|
172
|
-
check_block!(:ssh, definition)
|
|
173
|
-
Ssh.new(@values).instance_eval(&definition)
|
|
174
|
-
self
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# 设置设备命令的默认超时秒数。
|
|
178
|
-
def command_timeout(seconds)
|
|
179
|
-
@values[:command_timeout] = seconds
|
|
180
|
-
self
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# 设置避免设备自动换行的终端宽高。
|
|
184
|
-
def terminal_size(width, height = nil)
|
|
185
|
-
@values[:terminal_size] = height.nil? ? width : [width, height]
|
|
186
|
-
self
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# 绑定已加载的策略类;nil 明确关闭继承的业务能力。
|
|
190
|
-
def tftp_strategy(klass)
|
|
191
|
-
@values[:tftp_strategy] = klass
|
|
192
|
-
self
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def running_config_strategy(klass)
|
|
196
|
-
@values[:running_config_strategy] = klass
|
|
197
|
-
self
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def topology_strategy(klass)
|
|
201
|
-
@values[:topology_strategy] = klass
|
|
202
|
-
self
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# 将声明字段校验并构造成不可变档案。
|
|
206
|
-
def build = Profile.new(**@values)
|
|
207
|
-
|
|
208
|
-
private
|
|
209
|
-
|
|
210
|
-
# 块式声明入口必须有明确的子 DSL,避免静默忽略配置。
|
|
211
|
-
def check_block!(name, definition)
|
|
212
|
-
raise ArgumentError, "#{name} requires a block" unless definition
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# 命令集合的声明对象。
|
|
216
|
-
class Commands
|
|
217
|
-
attr_reader :config_commands, :save_commands
|
|
218
|
-
|
|
219
|
-
# 复制继承的命令,后续修改不会污染父类档案。
|
|
220
|
-
def initialize(config_commands, save_commands)
|
|
221
|
-
@config_commands = config_commands&.dup
|
|
222
|
-
@save_commands = save_commands.dup
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# 声明读取运行配置的命令序列。
|
|
226
|
-
def running_config(*commands)
|
|
227
|
-
@config_commands = commands.flatten
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# 声明保存配置的命令序列。
|
|
231
|
-
def save_config(*commands)
|
|
232
|
-
@save_commands = commands.flatten
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# 提示符的声明对象。
|
|
237
|
-
class Prompts
|
|
238
|
-
# 将登录、命令、用户名、密码和特权提示写入当前档案。
|
|
239
|
-
def initialize(values)
|
|
240
|
-
@values = values
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# 声明登录结束提示符。
|
|
244
|
-
def login(pattern) = @values[:login_prompt] = pattern
|
|
245
|
-
|
|
246
|
-
# 声明普通命令结束提示符。
|
|
247
|
-
def command(pattern) = @values[:command_prompt] = pattern
|
|
248
|
-
|
|
249
|
-
# 声明用户名输入提示。
|
|
250
|
-
def username(pattern) = @values[:username_prompt] = pattern
|
|
251
|
-
|
|
252
|
-
# 声明密码输入提示。
|
|
253
|
-
def password(pattern) = @values[:password_prompt] = pattern
|
|
254
|
-
|
|
255
|
-
# 声明特权模式提示符。
|
|
256
|
-
def privilege(pattern) = @values[:privilege_prompt] = pattern
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
# 分页规则的声明对象。
|
|
260
|
-
class Pager
|
|
261
|
-
# 将分页模式和响应写入当前档案。
|
|
262
|
-
def initialize(values)
|
|
263
|
-
@values = values
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# 声明分页提示匹配模式。
|
|
267
|
-
def pattern(value) = @values[:pager_pattern] = value
|
|
268
|
-
|
|
269
|
-
# 声明匹配分页提示后发送的字节。
|
|
270
|
-
def response(value) = @values[:pager_response] = value
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# 设备错误模式的声明对象。
|
|
274
|
-
class Errors
|
|
275
|
-
# 将错误模式列表写入当前档案。
|
|
276
|
-
def initialize(values)
|
|
277
|
-
@values = values
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# 追加认证失败模式。
|
|
281
|
-
def authentication(*patterns)
|
|
282
|
-
@values[:authentication_error_patterns] = patterns.flatten
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
# 追加命令失败模式。
|
|
286
|
-
def command(*patterns)
|
|
287
|
-
@values[:command_error_patterns] = patterns.flatten
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# 登录和命令交互的声明对象。
|
|
292
|
-
class Interactions
|
|
293
|
-
attr_reader :login_interactions, :confirmation_interactions
|
|
294
|
-
|
|
295
|
-
# 复制继承的交互规则,子类可以在其上追加设备差异。
|
|
296
|
-
def initialize(values)
|
|
297
|
-
@login_interactions = values[:login_interactions].dup
|
|
298
|
-
@confirmation_interactions = values[:confirmation_interactions].dup
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# 声明登录阶段的一次性应答。
|
|
302
|
-
def login(pattern, response:, sensitive: false, limit: 1, capture: true)
|
|
303
|
-
@login_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
304
|
-
limit: limit, capture: capture)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# 声明命令阶段的确认应答。
|
|
308
|
-
def confirm(pattern, response:, sensitive: false, limit: nil, capture: true)
|
|
309
|
-
@confirmation_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
310
|
-
limit: limit, capture: capture)
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# 特权认证规则的声明对象。
|
|
315
|
-
class Privilege
|
|
316
|
-
# 将特权命令和结束提示写入当前档案。
|
|
317
|
-
def initialize(values)
|
|
318
|
-
@values = values
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# 声明进入特权模式的设备命令。
|
|
322
|
-
def command(value) = @values[:privilege_command] = value
|
|
323
|
-
|
|
324
|
-
# 声明特权命令完成后的提示符。
|
|
325
|
-
def prompt(value) = @values[:privilege_prompt] = value
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# SSH 兼容参数的声明对象。
|
|
329
|
-
class Ssh
|
|
330
|
-
# 将旧版 SSH 参数写入当前档案。
|
|
331
|
-
def initialize(values)
|
|
332
|
-
@values = values
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
# 声明连接旧版设备所需的参数序列。
|
|
336
|
-
def legacy_arguments(*arguments) = @values[:legacy_ssh_arguments] = arguments.flatten
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
|
|
340
113
|
private
|
|
341
114
|
|
|
342
115
|
# 校验静态接口,不构造策略实例或触发设备 I/O。
|
|
@@ -444,3 +217,5 @@ module Net
|
|
|
444
217
|
end
|
|
445
218
|
end
|
|
446
219
|
end
|
|
220
|
+
|
|
221
|
+
require_relative "profile/builder"
|
|
@@ -6,10 +6,13 @@ module Net
|
|
|
6
6
|
module Connector
|
|
7
7
|
class RunningConfig
|
|
8
8
|
class Strategy
|
|
9
|
+
# 保存策略所属设备,供提示符和厂商校验使用。
|
|
9
10
|
def initialize(device) = @device = device
|
|
10
11
|
|
|
12
|
+
# 默认保留设备响应原文。
|
|
11
13
|
def clean(text) = text
|
|
12
14
|
|
|
15
|
+
# 默认取脚本最后一个完成步骤作为运行配置。
|
|
13
16
|
def result_step(result) = result.steps.last
|
|
14
17
|
|
|
15
18
|
# 普通采集命令保持视图不变;需要切换视图的采集策略显式调整。
|
|
@@ -17,11 +20,13 @@ module Net
|
|
|
17
20
|
TerminalRenderer.render(@device.current_prompt.to_s).lines.last.to_s.strip
|
|
18
21
|
end
|
|
19
22
|
|
|
23
|
+
# 公共策略不增加逐命令响应检查。
|
|
20
24
|
def check_response(_command, _response, _execution) end
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
# 使用终端行编辑规则渲染 H3C、Huawei 和 Radware 配置。
|
|
24
28
|
class Rendered < Strategy
|
|
29
|
+
# 应用终端行编辑规则,还原设备最终显示的配置文本。
|
|
25
30
|
def clean(text) = TerminalRenderer.render(text)
|
|
26
31
|
end
|
|
27
32
|
end
|
|
@@ -12,6 +12,7 @@ module Net
|
|
|
12
12
|
@device = device
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# 从设备档案选定本次采集策略;无厂商规则时使用公共策略。
|
|
15
16
|
def self.strategy(device)
|
|
16
17
|
(device.profile.running_config_strategy || Strategy).new(device)
|
|
17
18
|
end
|
|
@@ -33,6 +34,7 @@ module Net
|
|
|
33
34
|
|
|
34
35
|
private
|
|
35
36
|
|
|
37
|
+
# 只从完成的配置步骤提取非空内容,并保留此前所有步骤。
|
|
36
38
|
def finish(result)
|
|
37
39
|
step = @device.send(:config_result_step, result)
|
|
38
40
|
raise incomplete("configuration collection has no completed configuration step") unless step
|
|
@@ -44,6 +46,7 @@ module Net
|
|
|
44
46
|
Result.new(steps: result.steps, config: content)
|
|
45
47
|
end
|
|
46
48
|
|
|
49
|
+
# 用当前完整提示符构建结束匹配,避免配置正文中的单个 # 提前结束采集。
|
|
47
50
|
def prompt_for(command, strategy)
|
|
48
51
|
return command.prompt if command.prompt
|
|
49
52
|
|
|
@@ -54,6 +57,7 @@ module Net
|
|
|
54
57
|
/(?:\A|(?<=[\r\n]))[ \t\x00]*#{Regexp.escape(prompt)}[ \t\x00]*[\r\n]*\z/
|
|
55
58
|
end
|
|
56
59
|
|
|
60
|
+
# 排除命令回显和提示符,确认响应确实包含配置正文。
|
|
57
61
|
def content?(step)
|
|
58
62
|
body = TerminalRenderer.render(step.output.delete_suffix(step.prompt.to_s)).strip
|
|
59
63
|
lines = body.lines
|
|
@@ -61,6 +65,7 @@ module Net
|
|
|
61
65
|
!lines.join.strip.empty?
|
|
62
66
|
end
|
|
63
67
|
|
|
68
|
+
# 将缺少配置的情况统一映射为可识别的设备错误码。
|
|
64
69
|
def incomplete(message)
|
|
65
70
|
DeviceError.new(message, code: :incomplete_configuration, host: @device.host, phase: :collect)
|
|
66
71
|
end
|
|
@@ -112,6 +112,7 @@ module Net
|
|
|
112
112
|
# 敏感命令和交互的异常可能只引用秘密的一部分,不能只依赖完整词匹配。
|
|
113
113
|
def sensitive! = @sensitive = true
|
|
114
114
|
|
|
115
|
+
# 判断当前脱敏范围是否包含敏感交互。
|
|
115
116
|
def sensitive? = @sensitive
|
|
116
117
|
|
|
117
118
|
# 记住新的敏感字节,并按长度排序避免短词先匹配。
|
|
@@ -165,6 +166,7 @@ module Net
|
|
|
165
166
|
@pattern ||= /(?=(#{Regexp.union((@secrets + ["[REDACTED]"]).uniq.sort_by { |secret| -secret.bytesize })}))/n
|
|
166
167
|
end
|
|
167
168
|
|
|
169
|
+
# 合并重叠的秘密匹配区间,防止替换顺序露出部分凭据。
|
|
168
170
|
def matches(text)
|
|
169
171
|
ranges = []
|
|
170
172
|
text.to_enum(:scan, pattern).each do
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support"
|
|
4
|
-
require "active_support/logger"
|
|
5
|
-
require "active_support/tagged_logging"
|
|
6
3
|
require "fileutils"
|
|
4
|
+
require "logger"
|
|
7
5
|
require_relative "terminal_renderer"
|
|
8
6
|
require_relative "log_messages"
|
|
9
7
|
require_relative "errors"
|
|
@@ -29,14 +27,16 @@ module Net
|
|
|
29
27
|
if @configuration.log_format == :raw
|
|
30
28
|
@writer = @output
|
|
31
29
|
elsif @io || @configuration.logger
|
|
32
|
-
base = @configuration.logger ||
|
|
30
|
+
base = @configuration.logger || ::Logger.new(@io)
|
|
33
31
|
unless @configuration.logger
|
|
34
32
|
base.formatter = proc do |severity, time, _program, message|
|
|
35
33
|
"[#{time.getlocal.strftime("%Y-%m-%d %H:%M:%S %:z")}] #{severity} #{message}\n"
|
|
36
34
|
end
|
|
37
35
|
end
|
|
38
|
-
|
|
39
|
-
@logger
|
|
36
|
+
# 前缀直接写入消息,不克隆或修改调用方注入的日志器。
|
|
37
|
+
@logger = base
|
|
38
|
+
@event_level = [base.level, LEVELS.fetch(@configuration.log_level)].max
|
|
39
|
+
@tag = "[host=#{@configuration.host}] "
|
|
40
40
|
if @io && @configuration.log_level == :debug
|
|
41
41
|
@writer = TerminalRenderer.new(@output, max_line_bytes: @configuration.max_output_bytes)
|
|
42
42
|
end
|
|
@@ -67,7 +67,7 @@ module Net
|
|
|
67
67
|
end
|
|
68
68
|
safe_name = @redactor.call(name.to_s).scrub.gsub(/[[:cntrl:]]+/, " ").strip
|
|
69
69
|
finish_line
|
|
70
|
-
@logger.public_send(level, LogMessages.format(safe_name, values))
|
|
70
|
+
@logger.public_send(level, "#{@tag}#{LogMessages.format(safe_name, values)}") if LEVELS.fetch(level) >= @event_level
|
|
71
71
|
rescue Error
|
|
72
72
|
raise
|
|
73
73
|
rescue => error
|
|
@@ -92,7 +92,7 @@ module Net
|
|
|
92
92
|
return unless detailed? && !@writer
|
|
93
93
|
|
|
94
94
|
@redactor.call(TerminalRenderer.render(@redactor.call(bytes))).each_line do |line|
|
|
95
|
-
@logger.debug(" #{line.chomp}")
|
|
95
|
+
@logger.debug("#{@tag} #{line.chomp}") if ::Logger::DEBUG >= @event_level
|
|
96
96
|
end
|
|
97
97
|
rescue Error
|
|
98
98
|
raise
|
|
@@ -140,7 +140,7 @@ module Net
|
|
|
140
140
|
begin
|
|
141
141
|
@io&.close
|
|
142
142
|
ensure
|
|
143
|
-
@writer = @output = @logger = @io = @transport = nil
|
|
143
|
+
@writer = @output = @logger = @io = @transport = @tag = @event_level = nil
|
|
144
144
|
@attached = false
|
|
145
145
|
end
|
|
146
146
|
end
|
|
@@ -169,12 +169,14 @@ module Net
|
|
|
169
169
|
|
|
170
170
|
# 文件写入边界保留短尾部,避免分片或终端控制符拼出明文凭据。
|
|
171
171
|
class RedactingWriter
|
|
172
|
+
# 保存目标流、脱敏器和等待下一分片的尾部。
|
|
172
173
|
def initialize(target, redactor)
|
|
173
174
|
@target = target
|
|
174
175
|
@redactor = redactor
|
|
175
176
|
@pending = "".b
|
|
176
177
|
end
|
|
177
178
|
|
|
179
|
+
# 写入已确认安全的前缀,暂存可能与下一分片组成秘密的尾部。
|
|
178
180
|
def write(bytes)
|
|
179
181
|
@pending << bytes.b
|
|
180
182
|
safe, @pending = @redactor.stream_chunk(@pending)
|
|
@@ -182,8 +184,10 @@ module Net
|
|
|
182
184
|
bytes.bytesize
|
|
183
185
|
end
|
|
184
186
|
|
|
187
|
+
# 刷新目标流,仍不提前写出待判断的尾部。
|
|
185
188
|
def flush = @target.flush
|
|
186
189
|
|
|
190
|
+
# 在日志结束时脱敏并写出剩余尾部。
|
|
187
191
|
def finish
|
|
188
192
|
safe, @pending = @redactor.stream_chunk(@pending, final: true)
|
|
189
193
|
@target.write(safe)
|