netstack 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +22 -3
- data/{LICENSE.txt → LICENSE} +7 -6
- data/NOTICE +25 -0
- data/README.md +58 -20
- data/docs/acceptance.md +92 -0
- data/docs/backups.md +100 -0
- data/docs/configuration.md +198 -0
- data/docs/connector.md +187 -0
- data/docs/ddi.md +142 -0
- data/docs/deployment.md +47 -0
- data/docs/foundations.md +45 -0
- data/docs/implementation.md +68 -0
- data/docs/inventory.md +42 -0
- data/docs/monitoring.md +227 -0
- data/docs/motor_integration.md +96 -0
- data/docs/performance.md +39 -0
- data/docs/release.md +20 -0
- data/docs/vxlan.md +91 -0
- data/docs/wireless.md +81 -0
- data/lib/netstack/backups/artifact.rb +23 -0
- data/lib/netstack/backups/diff.rb +15 -0
- data/lib/netstack/backups/repository.rb +77 -0
- data/lib/netstack/backups/request.rb +78 -0
- data/lib/netstack/circuits/business_calendar.rb +51 -0
- data/lib/netstack/circuits/capacity_review.rb +191 -0
- data/lib/netstack/circuits/endpoint.rb +25 -0
- data/lib/netstack/circuits/monitoring_plan.rb +212 -0
- data/lib/netstack/configuration/artifact.rb +85 -0
- data/lib/netstack/configuration/artifact_resolver.rb +30 -0
- data/lib/netstack/configuration/bootstrap/payload.rb +67 -0
- data/lib/netstack/configuration/bootstrap/validator.rb +224 -0
- data/lib/netstack/configuration/catalog.rb +60 -0
- data/lib/netstack/configuration/generator.rb +87 -0
- data/lib/netstack/configuration/input_contract.rb +82 -0
- data/lib/netstack/configuration/payload.rb +168 -0
- data/lib/netstack/configuration/reader.rb +124 -0
- data/lib/netstack/configuration/support.rb +30 -0
- data/lib/netstack/configuration/system_configuration.rb +56 -0
- data/lib/netstack/configuration/template_manager.rb +174 -0
- data/lib/netstack/configuration/template_store.rb +40 -0
- data/lib/netstack/configuration/validator.rb +566 -0
- data/lib/netstack/connector/authentication.rb +77 -0
- data/lib/netstack/connector/authentication_error.rb +11 -0
- data/lib/netstack/connector/backup.rb +19 -0
- data/lib/netstack/connector/backup_busy.rb +11 -0
- data/lib/netstack/connector/backup_persistence_error.rb +32 -0
- data/lib/netstack/connector/command.rb +63 -0
- data/lib/netstack/connector/command_result.rb +24 -0
- data/lib/netstack/connector/command_timeout.rb +11 -0
- data/lib/netstack/connector/configuration.rb +138 -0
- data/lib/netstack/connector/connection_closed.rb +11 -0
- data/lib/netstack/connector/connection_error.rb +11 -0
- data/lib/netstack/connector/device.rb +284 -0
- data/lib/netstack/connector/device_error.rb +11 -0
- data/lib/netstack/connector/dialogue.rb +55 -0
- data/lib/netstack/connector/error.rb +44 -0
- data/lib/netstack/connector/error_metadata.rb +58 -0
- data/lib/netstack/connector/event.rb +24 -0
- data/lib/netstack/connector/execution.rb +110 -0
- data/lib/netstack/connector/interaction.rb +55 -0
- data/lib/netstack/connector/internal_error.rb +11 -0
- data/lib/netstack/connector/known_hosts.rb +128 -0
- data/lib/netstack/connector/local_backup.rb +63 -0
- data/lib/netstack/connector/log/event.rb +65 -0
- data/lib/netstack/connector/log/formatter.rb +18 -0
- data/lib/netstack/connector/log/messages.rb +54 -0
- data/lib/netstack/connector/log/redacting_writer.rb +33 -0
- data/lib/netstack/connector/log/transcript.rb +35 -0
- data/lib/netstack/connector/log.rb +262 -0
- data/lib/netstack/connector/log_error.rb +11 -0
- data/lib/netstack/connector/login_timeout.rb +11 -0
- data/lib/netstack/connector/output_limit_exceeded.rb +11 -0
- data/lib/netstack/connector/parsing_error.rb +11 -0
- data/lib/netstack/connector/profile/builder.rb +238 -0
- data/lib/netstack/connector/profile.rb +225 -0
- data/lib/netstack/connector/prompt_error.rb +11 -0
- data/lib/netstack/connector/recovery.rb +59 -0
- data/lib/netstack/connector/redactor.rb +100 -0
- data/lib/netstack/connector/response.rb +25 -0
- data/lib/netstack/connector/response_reader.rb +140 -0
- data/lib/netstack/connector/result.rb +50 -0
- data/lib/netstack/connector/running_config/rendered.rb +16 -0
- data/lib/netstack/connector/running_config/strategy.rb +28 -0
- data/lib/netstack/connector/running_config.rb +113 -0
- data/lib/netstack/connector/save_config.rb +23 -0
- data/lib/netstack/connector/script.rb +59 -0
- data/lib/netstack/connector/script_error.rb +11 -0
- data/lib/netstack/connector/script_output_limit_exceeded.rb +11 -0
- data/lib/netstack/connector/session.rb +414 -0
- data/lib/netstack/connector/session_busy.rb +11 -0
- data/lib/netstack/connector/storage/backup_lock.rb +117 -0
- data/lib/netstack/connector/storage/private_file.rb +111 -0
- data/lib/netstack/connector/storage/safe_file.rb +64 -0
- data/lib/netstack/connector/storage/saved_config.rb +76 -0
- data/lib/netstack/connector/terminal_renderer.rb +186 -0
- data/lib/netstack/connector/terminal_text.rb +32 -0
- data/lib/netstack/connector/textfsm.rb +71 -0
- data/lib/netstack/connector/tftp/file_upload.rb +53 -0
- data/lib/netstack/connector/tftp/strategy.rb +67 -0
- data/lib/netstack/connector/tftp.rb +116 -0
- data/lib/netstack/connector/tftp_completion_error.rb +36 -0
- data/lib/netstack/connector/tftp_receipt.rb +54 -0
- data/lib/netstack/connector/tftp_target.rb +65 -0
- data/lib/netstack/connector/topology/checkpoint_error.rb +16 -0
- data/lib/netstack/connector/topology/deployment_adapter.rb +163 -0
- data/lib/netstack/connector/topology/execution_result.rb +34 -0
- data/lib/netstack/connector/topology/immediate_strategy.rb +39 -0
- data/lib/netstack/connector/topology/interface_description.rb +46 -0
- data/lib/netstack/connector/topology/interface_name.rb +60 -0
- data/lib/netstack/connector/topology/plan.rb +105 -0
- data/lib/netstack/connector/topology/strategy.rb +76 -0
- data/lib/netstack/connector/topology.rb +255 -0
- data/lib/netstack/connector/transport_error.rb +11 -0
- data/lib/netstack/connector/transports.rb +201 -0
- data/lib/netstack/connector/underlying_error.rb +25 -0
- data/lib/netstack/connector/unsupported_operation.rb +11 -0
- data/lib/netstack/connector/vendors/cisco_ios/running_config.rb +22 -0
- data/lib/netstack/connector/vendors/cisco_ios/tftp_backup.rb +41 -0
- data/lib/netstack/connector/vendors/cisco_ios/topology.rb +54 -0
- data/lib/netstack/connector/vendors/cisco_ios.rb +44 -0
- data/lib/netstack/connector/vendors/cisco_nxos/running_config.rb +25 -0
- data/lib/netstack/connector/vendors/cisco_nxos/tftp_backup.rb +43 -0
- data/lib/netstack/connector/vendors/cisco_nxos.rb +51 -0
- data/lib/netstack/connector/vendors/h3c/tftp_backup.rb +34 -0
- data/lib/netstack/connector/vendors/h3c/topology.rb +72 -0
- data/lib/netstack/connector/vendors/h3c.rb +75 -0
- data/lib/netstack/connector/vendors/h3c_wireless.rb +13 -0
- data/lib/netstack/connector/vendors/hillstone/running_config.rb +16 -0
- data/lib/netstack/connector/vendors/hillstone/tftp_backup.rb +55 -0
- data/lib/netstack/connector/vendors/hillstone/topology.rb +64 -0
- data/lib/netstack/connector/vendors/hillstone.rb +39 -0
- data/lib/netstack/connector/vendors/huawei/tftp_backup.rb +26 -0
- data/lib/netstack/connector/vendors/huawei.rb +62 -0
- data/lib/netstack/connector/vendors/palo_alto/running_config.rb +77 -0
- data/lib/netstack/connector/vendors/palo_alto/tftp_backup.rb +49 -0
- data/lib/netstack/connector/vendors/palo_alto/topology.rb +77 -0
- data/lib/netstack/connector/vendors/palo_alto.rb +39 -0
- data/lib/netstack/connector/vendors/radware/running_config.rb +21 -0
- data/lib/netstack/connector/vendors/radware/tftp_backup.rb +49 -0
- data/lib/netstack/connector/vendors/radware/topology.rb +19 -0
- data/lib/netstack/connector/vendors/radware.rb +59 -0
- data/lib/netstack/connector/write_timeout.rb +11 -0
- data/lib/netstack/connector.rb +32 -0
- data/lib/netstack/ddi/applier.rb +107 -0
- data/lib/netstack/ddi/deployment.rb +137 -0
- data/lib/netstack/ddi/inputs.rb +61 -0
- data/lib/netstack/ddi/manager.rb +197 -0
- data/lib/netstack/ddi/plan.rb +99 -0
- data/lib/netstack/ddi/scope.rb +127 -0
- data/lib/netstack/ddi/settings.rb +44 -0
- data/lib/netstack/ddi/state.rb +166 -0
- data/lib/netstack/deployment/checkpoint_error.rb +14 -0
- data/lib/netstack/deployment/cli_adapter.rb +64 -0
- data/lib/netstack/deployment/executor.rb +131 -0
- data/lib/netstack/deployment/plan.rb +82 -0
- data/lib/netstack/deployment/receipt.rb +78 -0
- data/lib/netstack/deployment/recovery.rb +19 -0
- data/lib/netstack/deployment/step.rb +16 -0
- data/lib/netstack/dimension.rb +30 -0
- data/lib/netstack/error.rb +14 -0
- data/lib/netstack/input_error.rb +9 -0
- data/lib/netstack/integrations/bluecat/client.rb +84 -0
- data/lib/netstack/integrations/http/client.rb +146 -0
- data/lib/netstack/integrations/http/error.rb +10 -0
- data/lib/netstack/integrations/http/response.rb +28 -0
- data/lib/netstack/integrations/http_delivery/adapter.rb +51 -0
- data/lib/netstack/integrations/http_delivery/client.rb +127 -0
- data/lib/netstack/integrations/http_delivery/result.rb +27 -0
- data/lib/netstack/integrations/oxidized/client.rb +89 -0
- data/lib/netstack/integrations/zabbix/client.rb +211 -0
- data/lib/netstack/integrations/zabbix/error.rb +10 -0
- data/lib/netstack/inventory/address.rb +30 -0
- data/lib/netstack/inventory/changes.rb +13 -0
- data/lib/netstack/inventory/cli_parser.rb +75 -0
- data/lib/netstack/inventory/collector.rb +99 -0
- data/lib/netstack/inventory/device.rb +14 -0
- data/lib/netstack/inventory/interface.rb +25 -0
- data/lib/netstack/inventory/neighbor.rb +15 -0
- data/lib/netstack/inventory/reconciler.rb +74 -0
- data/lib/netstack/inventory/snapshot.rb +69 -0
- data/lib/netstack/inventory/transceiver.rb +23 -0
- data/lib/netstack/ipv4.rb +204 -0
- data/lib/netstack/ipv6.rb +88 -0
- data/lib/netstack/monitoring/applier.rb +179 -0
- data/lib/netstack/monitoring/device_plan.rb +23 -0
- data/lib/netstack/monitoring/host_plan.rb +152 -0
- data/lib/netstack/monitoring/inputs.rb +73 -0
- data/lib/netstack/monitoring/measurements.rb +89 -0
- data/lib/netstack/monitoring/mutation.rb +37 -0
- data/lib/netstack/monitoring/plan.rb +69 -0
- data/lib/netstack/monitoring/probe_plan.rb +19 -0
- data/lib/netstack/monitoring/state_comparison.rb +37 -0
- data/lib/netstack/range_set.rb +181 -0
- data/lib/netstack/routing/lookup.rb +46 -0
- data/lib/netstack/routing/lookup_result.rb +14 -0
- data/lib/netstack/routing/route.rb +32 -0
- data/lib/netstack/routing/snapshot.rb +23 -0
- data/lib/netstack/target.rb +15 -0
- data/lib/netstack/unsupported_capability.rb +9 -0
- data/lib/netstack/values.rb +70 -0
- data/lib/netstack/version.rb +1 -1
- data/lib/netstack/vxlan/auditor.rb +187 -0
- data/lib/netstack/vxlan/cisco_nxos/config_parser.rb +327 -0
- data/lib/netstack/vxlan/cisco_nxos/deployment_adapter.rb +63 -0
- data/lib/netstack/vxlan/cisco_nxos/runtime_parser.rb +182 -0
- data/lib/netstack/vxlan/cisco_nxos/segment_renderer.rb +42 -0
- data/lib/netstack/vxlan/collector.rb +77 -0
- data/lib/netstack/vxlan/fabric.rb +29 -0
- data/lib/netstack/vxlan/finding.rb +20 -0
- data/lib/netstack/vxlan/node.rb +51 -0
- data/lib/netstack/vxlan/route_policy.rb +57 -0
- data/lib/netstack/vxlan/runtime_result.rb +24 -0
- data/lib/netstack/vxlan/segment.rb +35 -0
- data/lib/netstack/vxlan/segment_plan.rb +25 -0
- data/lib/netstack/vxlan/segment_planner.rb +156 -0
- data/lib/netstack/vxlan/snapshot.rb +34 -0
- data/lib/netstack/vxlan/validation.rb +58 -0
- data/lib/netstack/vxlan/verifier.rb +52 -0
- data/lib/netstack/vxlan/vpc_domain.rb +20 -0
- data/lib/netstack/wireless/access_point.rb +12 -0
- data/lib/netstack/wireless/ap_group.rb +12 -0
- data/lib/netstack/wireless/assessment.rb +13 -0
- data/lib/netstack/wireless/assessor.rb +83 -0
- data/lib/netstack/wireless/authentication_connection.rb +12 -0
- data/lib/netstack/wireless/bss.rb +12 -0
- data/lib/netstack/wireless/client.rb +12 -0
- data/lib/netstack/wireless/collector.rb +191 -0
- data/lib/netstack/wireless/configuration.rb +13 -0
- data/lib/netstack/wireless/finding.rb +12 -0
- data/lib/netstack/wireless/h3c/config_parser.rb +229 -0
- data/lib/netstack/wireless/h3c/lldp_parser.rb +69 -0
- data/lib/netstack/wireless/h3c/runtime_parser.rb +240 -0
- data/lib/netstack/wireless/huawei/runtime_parser.rb +83 -0
- data/lib/netstack/wireless/invalid_snapshot.rb +11 -0
- data/lib/netstack/wireless/neighbor.rb +12 -0
- data/lib/netstack/wireless/normalizer.rb +90 -0
- data/lib/netstack/wireless/parse_result.rb +13 -0
- data/lib/netstack/wireless/radio.rb +12 -0
- data/lib/netstack/wireless/reconciler.rb +115 -0
- data/lib/netstack/wireless/reconciliation.rb +11 -0
- data/lib/netstack/wireless/security_policy.rb +12 -0
- data/lib/netstack/wireless/service_template.rb +12 -0
- data/lib/netstack/wireless/service_template_bindings.rb +56 -0
- data/lib/netstack/wireless/snapshot.rb +150 -0
- data/lib/netstack/wireless/state.rb +23 -0
- data/lib/netstack/wireless/view.rb +154 -0
- data/lib/netstack.rb +10 -3
- data/resources/configuration/cisco-bootstrap.erb +118 -0
- data/resources/configuration/cisco-distributed-gateway.erb +40 -0
- data/resources/configuration/cisco-n9k-vpc.erb +62 -0
- data/resources/configuration/cisco-server-port.erb +43 -0
- data/resources/configuration/h3c-bootstrap.erb +108 -0
- data/resources/configuration/h3c-irf.erb +57 -0
- data/resources/configuration/h3c-server-port.erb +36 -0
- data/resources/configuration/hillstone-ipsec-vpn.erb +68 -0
- data/resources/configuration/hillstone-policy.erb +31 -0
- data/resources/configuration/paloalto-ipsec-vpn.erb +39 -0
- data/resources/configuration/radware-load-balancer.erb +26 -0
- data/resources/textfsm/cisco_cdp_neighbors_detail.textfsm +7 -0
- data/resources/textfsm/cisco_ios_running_config_interfaces.textfsm +12 -0
- data/resources/textfsm/cisco_ios_show_ip_interface_brief.textfsm +7 -0
- data/resources/textfsm/h3c_interface_descriptions.textfsm +11 -0
- data/resources/textfsm/h3c_lldp_local_first.textfsm +8 -0
- data/resources/textfsm/h3c_lldp_name_first.textfsm +8 -0
- data/resources/textfsm/hillstone_interface_descriptions.textfsm +11 -0
- data/resources/textfsm/hillstone_lldp_neighbors.textfsm +7 -0
- data/resources/textfsm/index +8 -0
- data/resources/textfsm/palo_alto_interface_descriptions.textfsm +6 -0
- data/resources/textfsm/palo_alto_lldp_neighbors.textfsm +11 -0
- data/resources/textfsm/radware_port_names.textfsm +13 -0
- metadata +588 -27
- data/.rubocop.yml +0 -13
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -40
- data/Rakefile +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -8
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
|
|
4
|
+
|
|
5
|
+
module Netstack
|
|
6
|
+
module Connector
|
|
7
|
+
class Log
|
|
8
|
+
|
|
9
|
+
# 已渲染、已脱敏的字节按行变成事件,半行在上下文切换前收尾。
|
|
10
|
+
class Transcript
|
|
11
|
+
def initialize(&on_line)
|
|
12
|
+
@on_line = on_line
|
|
13
|
+
@buffer = +"".b
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(bytes)
|
|
17
|
+
@buffer << bytes
|
|
18
|
+
while (ending = @buffer.index("\n"))
|
|
19
|
+
@on_line.call(@buffer.slice!(0, ending + 1).chomp)
|
|
20
|
+
end
|
|
21
|
+
bytes.bytesize
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def flush; end
|
|
25
|
+
|
|
26
|
+
def finish
|
|
27
|
+
@on_line.call(@buffer) unless @buffer.empty?
|
|
28
|
+
@buffer = +"".b
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private_constant :RedactingWriter, :Transcript
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
|
|
4
|
+
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "logger"
|
|
7
|
+
require "securerandom"
|
|
8
|
+
|
|
9
|
+
module Netstack
|
|
10
|
+
module Connector
|
|
11
|
+
# 单个设备会话的日志,按级别控制是否包含完整回显。
|
|
12
|
+
class Log
|
|
13
|
+
LEVELS = { debug: ::Logger::DEBUG, info: ::Logger::INFO,
|
|
14
|
+
warn: ::Logger::WARN, error: ::Logger::ERROR }.freeze
|
|
15
|
+
CONTEXT_FIELDS = %w[host session_id operation command_id text source line].freeze
|
|
16
|
+
private_constant :CONTEXT_FIELDS
|
|
17
|
+
|
|
18
|
+
# 保存日志配置和敏感信息脱敏器。
|
|
19
|
+
def initialize(configuration, redactor:)
|
|
20
|
+
@configuration = configuration
|
|
21
|
+
@redactor = redactor
|
|
22
|
+
@context = {}.freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 为会话打开日志文件或应用日志器。
|
|
26
|
+
def open(transport)
|
|
27
|
+
@transport = transport
|
|
28
|
+
@session_id = SecureRandom.hex(8).freeze
|
|
29
|
+
@command_sequence = 0
|
|
30
|
+
@io = open_file(@configuration.log_file) if @configuration.log_file
|
|
31
|
+
if @configuration.log_format == :raw
|
|
32
|
+
@writer = @output = RedactingWriter.new(@io, @redactor) if @io
|
|
33
|
+
elsif @io || @configuration.logger
|
|
34
|
+
@logger = @configuration.logger || ::Logger.new(@io, formatter: Formatter.new)
|
|
35
|
+
open_transcript if @io && @configuration.log_level == :debug
|
|
36
|
+
end
|
|
37
|
+
rescue => error
|
|
38
|
+
close_preserving_error
|
|
39
|
+
raise build_error("unable to open session log", error), cause: nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 上下文只在持有会话锁期间使用;收尾必须先于恢复父上下文。
|
|
43
|
+
def with_context(**fields)
|
|
44
|
+
previous = @context
|
|
45
|
+
failed = false
|
|
46
|
+
finish_output
|
|
47
|
+
@context = @context.merge(fields).freeze
|
|
48
|
+
yield
|
|
49
|
+
rescue Exception # rubocop:disable Lint/RescueException -- Preserve this body's failure, including interruption, during log cleanup.
|
|
50
|
+
failed = true
|
|
51
|
+
raise
|
|
52
|
+
ensure
|
|
53
|
+
begin
|
|
54
|
+
finish_output
|
|
55
|
+
rescue Error
|
|
56
|
+
raise unless failed
|
|
57
|
+
ensure
|
|
58
|
+
@context = previous
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with_operation_context(name, &)
|
|
63
|
+
with_context(operation: name || @context[:operation] || :script, &)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def with_command_context(command, &)
|
|
67
|
+
@command_sequence += 1
|
|
68
|
+
with_context(command_id: @command_sequence, phase: :command,
|
|
69
|
+
text: command.sensitive? ? "[REDACTED]" : command.text,
|
|
70
|
+
source: command.source, line: command.line, &)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 将终端回显接入当前会话日志。
|
|
74
|
+
def attach
|
|
75
|
+
return unless @transport && @writer
|
|
76
|
+
|
|
77
|
+
@transport.log_output = self
|
|
78
|
+
@attached = true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 判断当前日志级别是否需要完整设备回显。
|
|
82
|
+
def debug? = @configuration.log_level == :debug && (!!@writer || enabled?(:debug))
|
|
83
|
+
|
|
84
|
+
# 按日志级别写入已脱敏的业务事件。
|
|
85
|
+
def log_event(name, level: :info, **fields)
|
|
86
|
+
return unless @logger || @configuration.on_event
|
|
87
|
+
|
|
88
|
+
finish_output
|
|
89
|
+
write_event(name, level: level, **fields)
|
|
90
|
+
rescue Error
|
|
91
|
+
raise
|
|
92
|
+
rescue => error
|
|
93
|
+
raise build_error("unable to write session event", error), cause: nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 异常正文由会话先脱敏;类型、错误码和阶段只允许共享词表中的值进入日志。
|
|
97
|
+
def log_failure(name, failure, phase: nil, **fields)
|
|
98
|
+
log_event(name, level: :error, **fields,
|
|
99
|
+
error: ErrorMetadata.type(failure.class.name) || "StandardError",
|
|
100
|
+
code: ErrorMetadata.code(failure.code),
|
|
101
|
+
phase: ErrorMetadata.phase(failure.phase) || ErrorMetadata.phase(phase),
|
|
102
|
+
message: failure.message)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 用户钩子可能把未登记的配置片段放进事件名、字段名或值;敏感范围统一隐藏。
|
|
106
|
+
def log_custom_event(name, level: :info, **fields)
|
|
107
|
+
if @redactor.sensitive?
|
|
108
|
+
log_event("custom", level: level, details: "[REDACTED]")
|
|
109
|
+
else
|
|
110
|
+
log_event(name, level: level, **fields)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# 登录完成并收集动态口令后,再记录已脱敏的认证回显。
|
|
115
|
+
def log_authentication_output(bytes)
|
|
116
|
+
return unless debug?
|
|
117
|
+
|
|
118
|
+
with_context(phase: :login) do
|
|
119
|
+
log_event("login_output", level: :debug)
|
|
120
|
+
if @writer
|
|
121
|
+
write(@redactor.call(bytes))
|
|
122
|
+
flush
|
|
123
|
+
else
|
|
124
|
+
log_response_output(bytes)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# 注入日志器与文件回显共用事件格式;两次脱敏覆盖终端控制符拼接。
|
|
130
|
+
def log_response_output(bytes)
|
|
131
|
+
return unless debug? && !@writer && !@redactor.output_sensitive?
|
|
132
|
+
|
|
133
|
+
@redactor.call(TerminalRenderer.render(@redactor.call(bytes))).each_line do |line|
|
|
134
|
+
write_event("device_output", level: :debug, output: line.chomp)
|
|
135
|
+
end
|
|
136
|
+
rescue Error
|
|
137
|
+
raise
|
|
138
|
+
rescue => error
|
|
139
|
+
raise build_error("unable to write device output", error), cause: nil
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# 向终端日志写入已处理的回显字节。
|
|
143
|
+
def write(bytes)
|
|
144
|
+
return unless @writer && !@redactor.output_sensitive?
|
|
145
|
+
|
|
146
|
+
@writer.write(bytes)
|
|
147
|
+
rescue Error
|
|
148
|
+
raise
|
|
149
|
+
rescue => error
|
|
150
|
+
raise build_error("unable to write session log", error), cause: nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# 刷新日志缓冲区。
|
|
154
|
+
def flush
|
|
155
|
+
@writer&.flush
|
|
156
|
+
@io&.flush
|
|
157
|
+
rescue Error
|
|
158
|
+
raise
|
|
159
|
+
rescue => error
|
|
160
|
+
raise build_error("unable to flush session log", error), cause: nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# 执行代码块期间暂停自动记录传输回显。
|
|
164
|
+
def pause
|
|
165
|
+
finish_output
|
|
166
|
+
@transport.log_output = nil if @transport
|
|
167
|
+
yield
|
|
168
|
+
ensure
|
|
169
|
+
@transport.log_output = self if @transport && @writer && @attached
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# 断开回显记录并关闭日志资源。
|
|
173
|
+
def close
|
|
174
|
+
begin
|
|
175
|
+
@transport.log_output = nil if @transport
|
|
176
|
+
finish_output
|
|
177
|
+
ensure
|
|
178
|
+
begin
|
|
179
|
+
@io&.close
|
|
180
|
+
ensure
|
|
181
|
+
@writer = @output = @transcript = @logger = @io = @transport = @session_id = nil
|
|
182
|
+
@attached = false
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
rescue Error
|
|
186
|
+
raise
|
|
187
|
+
rescue => error
|
|
188
|
+
raise build_error("unable to close session log", error), cause: nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
# 每次读取调用方的当前阈值,不缓存级别、不改写共享 Logger。
|
|
194
|
+
def enabled?(level)
|
|
195
|
+
LEVELS.fetch(level) >= LEVELS.fetch(@configuration.log_level) &&
|
|
196
|
+
(@configuration.on_event || (@logger && LEVELS.fetch(level) >= @logger.level))
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def write_event(name, level:, **fields)
|
|
200
|
+
return unless enabled?(level)
|
|
201
|
+
|
|
202
|
+
values = @context.compact.merge(fields.reject { |key, _| CONTEXT_FIELDS.include?(key.to_s) })
|
|
203
|
+
values = values.merge(host: @configuration.host, session_id: @session_id)
|
|
204
|
+
event = Event.new(name, values, redactor: @redactor)
|
|
205
|
+
@logger.public_send(level, event) if @logger && LEVELS.fetch(level) >= @logger.level
|
|
206
|
+
@configuration.on_event&.call(event)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def open_transcript
|
|
210
|
+
@transcript = Transcript.new { |line| write_event("device_output", level: :debug, output: line) }
|
|
211
|
+
@output = RedactingWriter.new(@transcript, @redactor)
|
|
212
|
+
@writer = TerminalRenderer.new(@output, max_line_bytes: @configuration.max_output_bytes)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# 先渲染,再结束过滤流,最后发出半行事件;顺序不能反转。
|
|
216
|
+
def finish_output
|
|
217
|
+
@writer.finish if @writer.respond_to?(:finish)
|
|
218
|
+
@output&.finish unless @output.equal?(@writer)
|
|
219
|
+
@transcript&.finish
|
|
220
|
+
rescue Error
|
|
221
|
+
raise
|
|
222
|
+
rescue => error
|
|
223
|
+
raise build_error("unable to finish session output", error), cause: nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# 非阻塞打开后检查同一文件描述符,拒绝 FIFO 等特殊文件,避免连接前无限等待。
|
|
227
|
+
# 仅常规文件可配置私有权限并接收设备日志。
|
|
228
|
+
def open_file(path)
|
|
229
|
+
FileUtils.mkdir_p(File.dirname(path), mode: 0o700)
|
|
230
|
+
file = File.open(path, File::WRONLY | File::CREAT | File::APPEND | File::NOFOLLOW | File::NONBLOCK, 0o600)
|
|
231
|
+
begin
|
|
232
|
+
raise IOError, "session log must be a regular file" unless file.stat.file?
|
|
233
|
+
|
|
234
|
+
file.chmod(0o600)
|
|
235
|
+
file.binmode
|
|
236
|
+
file.write("\n") if file.size.positive? && @configuration.log_format == :text
|
|
237
|
+
file
|
|
238
|
+
rescue Exception # rubocop:disable Lint/RescueException -- Release partially configured files on interrupts too.
|
|
239
|
+
begin
|
|
240
|
+
file.close
|
|
241
|
+
rescue
|
|
242
|
+
# 保留设置失败的原始原因。
|
|
243
|
+
end
|
|
244
|
+
raise
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# 在日志打开失败后尽力释放资源。
|
|
249
|
+
def close_preserving_error
|
|
250
|
+
close
|
|
251
|
+
rescue
|
|
252
|
+
nil
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# 将日志异常包装成统一错误并保留脱敏原因。
|
|
256
|
+
def build_error(message, error)
|
|
257
|
+
LogError.new(message, phase: :logging,
|
|
258
|
+
underlying: UnderlyingError.new(error, @redactor, sensitive: @redactor.sensitive?))
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adapted from net-connector; copyright (c) 2026 net-connector contributors (MIT).
|
|
4
|
+
|
|
5
|
+
module Netstack
|
|
6
|
+
module Connector
|
|
7
|
+
class Profile
|
|
8
|
+
# 构造器把声明块中的领域语言转换为设备档案字段。
|
|
9
|
+
class Builder
|
|
10
|
+
# 保存继承值并提供 commands、prompts 等有限的声明入口。
|
|
11
|
+
def initialize(parent)
|
|
12
|
+
@values = parent.to_h
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# 声明运行配置和保存配置命令。
|
|
16
|
+
def commands(&definition)
|
|
17
|
+
validate_block!(:commands, definition)
|
|
18
|
+
commands = Commands.new(
|
|
19
|
+
@values[:config_commands], @values[:save_commands]
|
|
20
|
+
)
|
|
21
|
+
commands.instance_eval(&definition)
|
|
22
|
+
@values[:config_commands] = commands.config_commands
|
|
23
|
+
@values[:save_commands] = commands.save_commands
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 声明登录、命令、凭据和提权提示符。
|
|
28
|
+
def prompts(&definition)
|
|
29
|
+
validate_block!(:prompts, definition)
|
|
30
|
+
Prompts.new(@values).instance_eval(&definition)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 声明分页模式及自动翻页响应。
|
|
35
|
+
def pager(&definition)
|
|
36
|
+
validate_block!(:pager, definition)
|
|
37
|
+
Pager.new(@values).instance_eval(&definition)
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 声明认证失败和命令失败的输出模式。
|
|
42
|
+
def errors(&definition)
|
|
43
|
+
validate_block!(:errors, definition)
|
|
44
|
+
Errors.new(@values).instance_eval(&definition)
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 声明登录挑战和命令确认交互。
|
|
49
|
+
def interactions(&definition)
|
|
50
|
+
validate_block!(:interactions, definition)
|
|
51
|
+
interactions = Interactions.new(@values)
|
|
52
|
+
interactions.instance_eval(&definition)
|
|
53
|
+
@values[:login_interactions] = interactions.login_interactions
|
|
54
|
+
@values[:confirmation_interactions] = interactions.confirmation_interactions
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# 声明进入特权模式所需的命令和提示符。
|
|
59
|
+
def privilege(&definition)
|
|
60
|
+
validate_block!(:privilege, definition)
|
|
61
|
+
Privilege.new(@values).instance_eval(&definition)
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 声明旧版 SSH 协商参数。
|
|
66
|
+
def ssh(&definition)
|
|
67
|
+
validate_block!(:ssh, definition)
|
|
68
|
+
Ssh.new(@values).instance_eval(&definition)
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 设置设备命令的默认超时秒数。
|
|
73
|
+
def command_timeout(seconds)
|
|
74
|
+
@values[:command_timeout] = seconds
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 设置避免设备自动换行的终端宽高。
|
|
79
|
+
def terminal_size(width, height = nil)
|
|
80
|
+
@values[:terminal_size] = height.nil? ? width : [width, height]
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 绑定已加载的策略类;nil 明确关闭继承的业务能力。
|
|
85
|
+
def tftp_strategy(klass)
|
|
86
|
+
@values[:tftp_strategy] = klass
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# 指定运行配置采集策略,覆盖继承的厂商规则。
|
|
91
|
+
def running_config_strategy(klass)
|
|
92
|
+
@values[:running_config_strategy] = klass
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 指定邻居发现和描述下发策略,nil 表示关闭能力。
|
|
97
|
+
def topology_strategy(klass)
|
|
98
|
+
@values[:topology_strategy] = klass
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# 将声明字段校验并构造成不可变档案。
|
|
103
|
+
def build = Profile.new(**@values)
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# 块式声明入口必须有明确的子 DSL,避免静默忽略配置。
|
|
108
|
+
def validate_block!(name, definition)
|
|
109
|
+
raise ArgumentError, "#{name} requires a block" unless definition
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 命令集合的声明对象。
|
|
113
|
+
class Commands
|
|
114
|
+
attr_reader :config_commands, :save_commands
|
|
115
|
+
|
|
116
|
+
# 复制继承的命令,后续修改不会污染父类档案。
|
|
117
|
+
def initialize(config_commands, save_commands)
|
|
118
|
+
@config_commands = config_commands&.dup
|
|
119
|
+
@save_commands = save_commands.dup
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 声明读取运行配置的命令序列。
|
|
123
|
+
def running_config(*commands)
|
|
124
|
+
@config_commands = commands.flatten
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 声明保存配置的命令序列。
|
|
128
|
+
def save_config(*commands)
|
|
129
|
+
@save_commands = commands.flatten
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# 提示符的声明对象。
|
|
134
|
+
class Prompts
|
|
135
|
+
# 将登录、命令、用户名、密码和特权提示写入当前档案。
|
|
136
|
+
def initialize(values)
|
|
137
|
+
@values = values
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# 声明登录结束提示符。
|
|
141
|
+
def login(pattern) = @values[:login_prompt] = pattern
|
|
142
|
+
|
|
143
|
+
# 声明普通命令结束提示符。
|
|
144
|
+
def command(pattern) = @values[:command_prompt] = pattern
|
|
145
|
+
|
|
146
|
+
# 声明用户名输入提示。
|
|
147
|
+
def username(pattern) = @values[:username_prompt] = pattern
|
|
148
|
+
|
|
149
|
+
# 声明密码输入提示。
|
|
150
|
+
def password(pattern) = @values[:password_prompt] = pattern
|
|
151
|
+
|
|
152
|
+
# 声明特权模式提示符。
|
|
153
|
+
def privilege(pattern) = @values[:privilege_prompt] = pattern
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# 分页规则的声明对象。
|
|
157
|
+
class Pager
|
|
158
|
+
# 将分页模式和响应写入当前档案。
|
|
159
|
+
def initialize(values)
|
|
160
|
+
@values = values
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# 声明分页提示匹配模式。
|
|
164
|
+
def pattern(value) = @values[:pager_pattern] = value
|
|
165
|
+
|
|
166
|
+
# 声明匹配分页提示后发送的字节。
|
|
167
|
+
def response(value) = @values[:pager_response] = value
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# 设备错误模式的声明对象。
|
|
171
|
+
class Errors
|
|
172
|
+
# 将错误模式列表写入当前档案。
|
|
173
|
+
def initialize(values)
|
|
174
|
+
@values = values
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# 替换认证失败模式列表。
|
|
178
|
+
def authentication(*patterns)
|
|
179
|
+
@values[:authentication_error_patterns] = patterns.flatten
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 替换命令失败模式列表。
|
|
183
|
+
def command(*patterns)
|
|
184
|
+
@values[:command_error_patterns] = patterns.flatten
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 登录和命令交互的声明对象。
|
|
189
|
+
class Interactions
|
|
190
|
+
attr_reader :login_interactions, :confirmation_interactions
|
|
191
|
+
|
|
192
|
+
# 复制继承的交互规则,子类可以在其上追加设备差异。
|
|
193
|
+
def initialize(values)
|
|
194
|
+
@login_interactions = values[:login_interactions].dup
|
|
195
|
+
@confirmation_interactions = values[:confirmation_interactions].dup
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# 声明登录阶段的一次性应答。
|
|
199
|
+
def login(pattern, response:, sensitive: false, limit: 1, capture: true)
|
|
200
|
+
@login_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
201
|
+
limit: limit, capture: capture)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# 声明命令阶段的确认应答。
|
|
205
|
+
def confirm(pattern, response:, sensitive: false, limit: nil, capture: true)
|
|
206
|
+
@confirmation_interactions << Interaction.new(pattern, response, sensitive: sensitive,
|
|
207
|
+
limit: limit, capture: capture)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# 特权认证规则的声明对象。
|
|
212
|
+
class Privilege
|
|
213
|
+
# 将特权命令和结束提示写入当前档案。
|
|
214
|
+
def initialize(values)
|
|
215
|
+
@values = values
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# 声明进入特权模式的设备命令。
|
|
219
|
+
def command(value) = @values[:privilege_command] = value
|
|
220
|
+
|
|
221
|
+
# 声明特权命令完成后的提示符。
|
|
222
|
+
def prompt(value) = @values[:privilege_prompt] = value
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# SSH 兼容参数的声明对象。
|
|
226
|
+
class Ssh
|
|
227
|
+
# 将旧版 SSH 参数写入当前档案。
|
|
228
|
+
def initialize(values)
|
|
229
|
+
@values = values
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# 声明连接旧版设备所需的参数序列。
|
|
233
|
+
def legacy_arguments(*arguments) = @values[:legacy_ssh_arguments] = arguments.flatten
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|