zabbix_manager 5.1.6 → 6.0.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 +44 -1
- data/README.md +73 -45
- data/lib/zabbix_manager/classes/actions.rb +3 -11
- data/lib/zabbix_manager/classes/applications.rb +2 -8
- data/lib/zabbix_manager/classes/configurations.rb +4 -9
- data/lib/zabbix_manager/classes/{drules.rb → discovery_rules.rb} +2 -8
- data/lib/zabbix_manager/classes/errors.rb +3 -0
- data/lib/zabbix_manager/classes/events.rb +2 -1
- data/lib/zabbix_manager/classes/graphs.rb +19 -15
- data/lib/zabbix_manager/classes/{hostgroups.rb → host_groups.rb} +5 -11
- data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
- data/lib/zabbix_manager/classes/hosts.rb +139 -89
- data/lib/zabbix_manager/classes/{httptests.rb → http_tests.rb} +2 -8
- data/lib/zabbix_manager/classes/items.rb +139 -92
- data/lib/zabbix_manager/classes/maintenance.rb +2 -1
- data/lib/zabbix_manager/classes/{mediatypes.rb → media_types.rb} +2 -7
- data/lib/zabbix_manager/classes/problems.rb +11 -40
- data/lib/zabbix_manager/classes/proxies.rb +7 -15
- data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
- data/lib/zabbix_manager/classes/roles.rb +13 -19
- data/lib/zabbix_manager/classes/screens.rb +11 -30
- data/lib/zabbix_manager/classes/scripts.rb +2 -8
- data/lib/zabbix_manager/classes/server.rb +1 -0
- data/lib/zabbix_manager/classes/templates.rb +2 -65
- data/lib/zabbix_manager/classes/triggers.rb +62 -53
- data/lib/zabbix_manager/classes/user_groups.rb +63 -0
- data/lib/zabbix_manager/classes/user_macros.rb +111 -0
- data/lib/zabbix_manager/classes/users.rb +15 -54
- data/lib/zabbix_manager/classes/value_maps.rb +52 -0
- data/lib/zabbix_manager/client.rb +162 -279
- data/lib/zabbix_manager/configuration.rb +115 -0
- data/lib/zabbix_manager/http_transport.rb +145 -97
- data/lib/zabbix_manager/log_sanitizer.rb +18 -29
- data/lib/zabbix_manager/monitoring/device.rb +394 -0
- data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
- data/lib/zabbix_manager/monitoring/line.rb +176 -0
- data/lib/zabbix_manager/monitoring/line_items.rb +110 -0
- data/lib/zabbix_manager/monitoring/line_lifecycle.rb +169 -0
- data/lib/zabbix_manager/monitoring/line_plan.rb +113 -0
- data/lib/zabbix_manager/monitoring/thresholds.rb +182 -0
- data/lib/zabbix_manager/monitoring/traffic_items.rb +133 -0
- data/lib/zabbix_manager/monitoring/validation.rb +92 -0
- data/lib/zabbix_manager/monitoring.rb +216 -642
- data/lib/zabbix_manager/resource.rb +262 -0
- data/lib/zabbix_manager/traffic.rb +239 -0
- data/lib/zabbix_manager/version.rb +1 -1
- data/lib/zabbix_manager.rb +66 -179
- data/zabbix_manager.gemspec +6 -22
- metadata +41 -190
- data/lib/zabbix_manager/basic/basic_alias.rb +0 -42
- data/lib/zabbix_manager/basic/basic_func.rb +0 -55
- data/lib/zabbix_manager/basic/basic_init.rb +0 -50
- data/lib/zabbix_manager/basic/basic_logic.rb +0 -239
- data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -189
- data/lib/zabbix_manager/classes/usergroups.rb +0 -75
- data/lib/zabbix_manager/classes/usermacros.rb +0 -222
- data/lib/zabbix_manager/classes/valuemaps.rb +0 -26
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ZabbixManager
|
|
4
|
+
# 原生值映射资源;所属范围与映射规则由调用方提供。
|
|
5
|
+
class ValueMaps < Resource
|
|
6
|
+
# 返回 Zabbix API 中值映射对象的方法名前缀。
|
|
7
|
+
#
|
|
8
|
+
# @return [String] 值映射对象的方法名前缀
|
|
9
|
+
def method_name
|
|
10
|
+
"valuemap"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 返回用于唯一识别值映射的业务字段名。
|
|
14
|
+
#
|
|
15
|
+
# @return [String] 值映射对象的业务标识字段名
|
|
16
|
+
def identify
|
|
17
|
+
"name"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 5.4 起值映射属于主机或模板;旧版仍按全局名称定位。
|
|
21
|
+
# @return [Hash] 带版本边界的精确身份条件
|
|
22
|
+
def identity_filter(data)
|
|
23
|
+
attributes = data.deep_symbolize_keys
|
|
24
|
+
identity = super(attributes)
|
|
25
|
+
if host_scoped?
|
|
26
|
+
raise Invalid, "hostid is required for a host-scoped value map" unless attributes.key?(:hostid)
|
|
27
|
+
|
|
28
|
+
identity[:hostid] = normalized_ids([attributes[:hostid]]).first
|
|
29
|
+
end
|
|
30
|
+
identity
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 名称查询始终经过完整身份检查,避免跨主机合法重名造成误选。
|
|
34
|
+
# @return [Integer, nil] 唯一值映射 ID
|
|
35
|
+
def get_id(data)
|
|
36
|
+
super(identity_filter(data))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# hostid 用于身份查询和创建,不能用于移动已有值映射。
|
|
40
|
+
# @return [Integer] 已确认的值映射 ID
|
|
41
|
+
def update(data)
|
|
42
|
+
attributes = data.deep_symbolize_keys
|
|
43
|
+
super(host_scoped? ? attributes.except(:hostid) : attributes)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def host_scoped?
|
|
49
|
+
Gem::Version.new(@client.api_version) >= Gem::Version.new("5.4")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -2,38 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "logger"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/logger"
|
|
7
|
+
require "active_support/tagged_logging"
|
|
5
8
|
|
|
6
9
|
class ZabbixManager
|
|
10
|
+
# 持有一个已认证连接,管理 JSON-RPC 请求的完整生命周期。
|
|
7
11
|
class Client
|
|
8
|
-
SUPPORTED_MAJOR_VERSIONS = (4..7)
|
|
12
|
+
SUPPORTED_MAJOR_VERSIONS = (4..7)
|
|
9
13
|
UNAUTHENTICATED_METHODS = %w[apiinfo.version user.login].freeze
|
|
10
|
-
BLANK_NORMALIZED_OPTIONS = %i[api_token username user password http_user http_password].freeze
|
|
11
14
|
UPSERT_MUTEX_STRIPES = 64
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@
|
|
15
|
+
LOG_LEVELS = { debug: Logger::DEBUG, info: Logger::INFO, warn: Logger::WARN,
|
|
16
|
+
error: Logger::ERROR, fatal: Logger::FATAL, unknown: Logger::UNKNOWN }.freeze
|
|
17
|
+
private_constant :LOG_LEVELS
|
|
18
|
+
|
|
19
|
+
attr_reader :options, :logger, :api_version
|
|
20
|
+
|
|
21
|
+
# 校验配置后立即查询服务器版本并完成认证。
|
|
22
|
+
# 初始化失败时关闭连接;成功后的客户端生命周期由调用方负责。
|
|
23
|
+
# @param options [Hash] 显式连接、凭据、日志及锁配置
|
|
24
|
+
# @raise [Invalid] 连接配置无效或相互冲突
|
|
25
|
+
# @raise [ApiError, TransportError, ProtocolError] 版本查询或认证失败
|
|
26
|
+
def initialize(**options)
|
|
27
|
+
@settings = Configuration.parse(options)
|
|
28
|
+
@options = sanitized_options.freeze
|
|
25
29
|
@logger = build_logger
|
|
26
30
|
@id_mutex = Mutex.new
|
|
27
31
|
@next_id = 0
|
|
28
32
|
@upsert_mutexes = Array.new(UPSERT_MUTEX_STRIPES) { Mutex.new }
|
|
29
|
-
|
|
30
|
-
@transport = HttpTransport.new(@raw_options)
|
|
33
|
+
@transport = HttpTransport.new(@settings)
|
|
31
34
|
validate_token_transport!
|
|
32
|
-
@api_version =
|
|
35
|
+
@api_version = api_request(method: "apiinfo.version")
|
|
33
36
|
validate_api_version!
|
|
34
37
|
validate_http_auth_compatibility!
|
|
35
|
-
@credential_type = @
|
|
36
|
-
@auth_token = @
|
|
38
|
+
@credential_type = @settings[:api_token] ? :api_token : :user_session
|
|
39
|
+
@auth_token = @settings[:api_token] || login
|
|
40
|
+
unless @auth_token.is_a?(String) && !@auth_token.empty?
|
|
41
|
+
raise ProtocolError, "Invalid authentication response"
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
log(:info, "client.connected", version: @api_version, credential_type: @credential_type)
|
|
38
45
|
log(:warn, "tls.verification_disabled", url: @transport.safe_url) if insecure_https?
|
|
39
46
|
log(:warn, "token.sent_over_http", url: @transport.safe_url) if insecure_token_transport?
|
|
@@ -42,324 +49,200 @@ class ZabbixManager
|
|
|
42
49
|
raise
|
|
43
50
|
end
|
|
44
51
|
|
|
45
|
-
#
|
|
46
|
-
# @
|
|
47
|
-
# @
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def api_version
|
|
56
|
-
@api_version ||= api_request(method: "apiinfo.version", params: {})
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# 使用用户名和密码创建 Zabbix 会话。
|
|
60
|
-
# @return [String] Zabbix session token
|
|
61
|
-
# @api semipublic
|
|
62
|
-
def auth
|
|
63
|
-
api_request(
|
|
64
|
-
method: "user.login",
|
|
65
|
-
params: {
|
|
66
|
-
login_parameter => username,
|
|
67
|
-
password: @raw_options[:password]
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# 判断是否启用调试日志。
|
|
73
|
-
# @return [Boolean]
|
|
74
|
-
# @api semipublic
|
|
75
|
-
def debug?
|
|
76
|
-
@raw_options[:debug] == true
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# 序列化 JSON-RPC 请求并按版本加入请求体认证。
|
|
80
|
-
# @return [String]
|
|
81
|
-
# @api semipublic
|
|
82
|
-
def message_json(body)
|
|
83
|
-
method = fetch_body_value(body, :method)
|
|
84
|
-
message = {
|
|
85
|
-
method: method,
|
|
86
|
-
params: fetch_body_value(body, :params) || {},
|
|
87
|
-
id: id,
|
|
88
|
-
jsonrpc: "2.0"
|
|
89
|
-
}
|
|
90
|
-
message[:auth] = @auth_token if @auth_token && body_authentication?(method)
|
|
91
|
-
JSON.generate(message)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# 通过持久连接发送请求,并按版本加入 Bearer 令牌。
|
|
95
|
-
# @return [String] raw response body
|
|
96
|
-
# @api semipublic
|
|
97
|
-
def http_request(body)
|
|
98
|
-
method = JSON.parse(body).fetch("method")
|
|
99
|
-
@transport.request(body, bearer_token: bearer_token_for(method))
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# 解析 JSON-RPC 响应并统一转换协议错误。
|
|
103
|
-
# @return [Object]
|
|
104
|
-
# @api semipublic
|
|
105
|
-
def _request(body)
|
|
106
|
-
parsed = JSON.parse(http_request(body))
|
|
107
|
-
raise_api_error(parsed, body) if parsed["error"]
|
|
108
|
-
unless parsed.key?("result")
|
|
109
|
-
raise ApiError.new("Invalid JSON-RPC response: missing result", LogSanitizer.sanitize(parsed))
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
parsed["result"]
|
|
113
|
-
rescue JSON::ParserError => e
|
|
114
|
-
raise ApiError, "Invalid JSON response: #{e.message}"
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# 格式化请求摘要并隐藏全部参数值。
|
|
118
|
-
# @return [String]
|
|
119
|
-
# @api semipublic
|
|
120
|
-
def pretty_body(body)
|
|
121
|
-
parsed = JSON.parse(body)
|
|
122
|
-
parsed["params"] = LogSanitizer::REDACTED if parsed.key?("params")
|
|
123
|
-
JSON.pretty_generate(LogSanitizer.sanitize(parsed))
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# 执行 Zabbix JSON-RPC 方法并记录脱敏耗时日志。
|
|
127
|
-
# @return [Object] decoded result value
|
|
128
|
-
# @api public
|
|
129
|
-
def api_request(body)
|
|
130
|
-
method = fetch_body_value(body, :method)
|
|
52
|
+
# 执行一次请求,不重放失败或结果未确认的变更。
|
|
53
|
+
# @param method [String] 原生 API 方法,例如 "history.get"
|
|
54
|
+
# @param params [Hash, Array] 原生 Zabbix 参数,不将参数值写入日志
|
|
55
|
+
# @return [Object] 解码后的 JSON-RPC 结果,保留服务器返回的标量类型
|
|
56
|
+
# @raise [Invalid] 方法名或参数容器无效
|
|
57
|
+
# @raise [ApiError] 服务器明确拒绝请求
|
|
58
|
+
# @raise [TransportError, ProtocolError] 未收到能够确认结果的有效响应
|
|
59
|
+
def api_request(method:, params: {})
|
|
60
|
+
method = method.dup.freeze if method.is_a?(String)
|
|
61
|
+
validate_request!(method, params)
|
|
131
62
|
started_at = monotonic_time
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
63
|
+
message = request_message(method, params)
|
|
64
|
+
log(:debug, "request.started", method: method, request_id: message[:id])
|
|
65
|
+
body = @transport.request(JSON.generate(message), bearer_token: bearer_token_for(method))
|
|
66
|
+
result = parse_response(body, message.fetch(:id))
|
|
67
|
+
log(:info, "request.completed", method: method, request_id: message[:id], duration_ms: elapsed_ms(started_at))
|
|
135
68
|
result
|
|
136
|
-
rescue StandardError =>
|
|
137
|
-
|
|
69
|
+
rescue StandardError => error
|
|
70
|
+
if started_at
|
|
71
|
+
log(:warn, "request.failed", method: method, request_id: message&.fetch(:id),
|
|
72
|
+
duration_ms: elapsed_ms(started_at), error: error.class.name)
|
|
73
|
+
end
|
|
138
74
|
raise
|
|
139
75
|
end
|
|
140
|
-
alias manager_request api_request
|
|
141
76
|
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# @
|
|
77
|
+
# 注销密码认证会话;API token 仅需清理本地状态。
|
|
78
|
+
# 远端注销失败时仍清除本地认证令牌并关闭传输连接。
|
|
79
|
+
# @return [Boolean] 服务器注销结果;API token 模式返回 true
|
|
80
|
+
# @raise [ApiError, TransportError] 远端会话注销失败
|
|
145
81
|
def logout
|
|
146
|
-
|
|
147
|
-
result
|
|
82
|
+
@credential_type == :user_session && @auth_token ? api_request(method: "user.logout", params: []) : true
|
|
148
83
|
ensure
|
|
149
84
|
@auth_token = nil
|
|
150
85
|
close
|
|
151
86
|
end
|
|
152
87
|
|
|
153
|
-
#
|
|
154
|
-
# @return [
|
|
155
|
-
# @api public
|
|
88
|
+
# 关闭连接但不撤销凭据;后续请求按需重新连接。
|
|
89
|
+
# @return [true]
|
|
156
90
|
def close
|
|
157
91
|
@transport.close
|
|
158
92
|
end
|
|
159
93
|
|
|
160
|
-
#
|
|
161
|
-
# @
|
|
162
|
-
# @
|
|
94
|
+
# 延迟构造日志元数据;日志失败不能改变远端写入结果。
|
|
95
|
+
# @param level [Symbol] Ruby Logger 日志级别方法
|
|
96
|
+
# @param event [String] 稳定的事件名称
|
|
97
|
+
# @param data [Hash] 序列化之前经过脱敏的元数据
|
|
98
|
+
# @return [Object, nil] 日志返回值;日志关闭或失败时返回 nil
|
|
163
99
|
def log(level, event, data = {})
|
|
164
100
|
return unless @logger
|
|
165
101
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
102
|
+
severity = LOG_LEVELS[level]
|
|
103
|
+
return if severity && @logger.respond_to?(:level) && @logger.level > severity
|
|
104
|
+
|
|
105
|
+
@logger.tagged("zabbix_manager") do
|
|
106
|
+
@logger.public_send(level) { JSON.generate(LogSanitizer.sanitize(data.merge(event: event))) }
|
|
107
|
+
end
|
|
108
|
+
rescue StandardError
|
|
109
|
+
nil
|
|
169
110
|
end
|
|
170
111
|
|
|
171
|
-
#
|
|
172
|
-
# @return [String]
|
|
173
|
-
# @api public
|
|
112
|
+
# @return [String] 仅包含脱敏配置的诊断摘要
|
|
174
113
|
def inspect
|
|
175
|
-
"#<#{self.class} options=#{@
|
|
114
|
+
"#<#{self.class} options=#{@options.inspect}>"
|
|
176
115
|
end
|
|
177
116
|
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
# @
|
|
117
|
+
# 应用层锁适配器可将本地串行化扩展到多个工作进程。
|
|
118
|
+
# 锁不可重入;调用方不能在块内再次获取 upsert 锁。
|
|
119
|
+
# @param key [String] 被保护操作的稳定、非秘密身份
|
|
120
|
+
# @yield 需要串行执行的读取、修改和写入操作
|
|
121
|
+
# @return [Object] 块的返回值
|
|
181
122
|
def with_upsert_lock(key, &block)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
coordinator = @raw_options[:upsert_lock]
|
|
123
|
+
@upsert_mutexes[key.hash % UPSERT_MUTEX_STRIPES].synchronize do
|
|
124
|
+
coordinator = @settings[:upsert_lock]
|
|
185
125
|
coordinator ? coordinator.call(key, &block) : yield
|
|
186
126
|
end
|
|
187
127
|
end
|
|
188
128
|
|
|
189
129
|
private
|
|
190
130
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def normalize_options(options)
|
|
195
|
-
unless options.respond_to?(:each_pair)
|
|
196
|
-
raise Invalid, "options must be a hash-like object"
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
normalized = options.to_h.transform_keys(&:to_sym)
|
|
200
|
-
BLANK_NORMALIZED_OPTIONS.each do |key|
|
|
201
|
-
value = normalized[key].presence
|
|
202
|
-
value ? normalized[key] = value : normalized.delete(key)
|
|
203
|
-
end
|
|
204
|
-
normalized
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
# 脱敏普通凭据,并清理 URL 和代理地址中的 userinfo 与查询串。
|
|
208
|
-
def sanitize_options(options)
|
|
209
|
-
sanitized = LogSanitizer.sanitize(options)
|
|
131
|
+
def sanitized_options
|
|
132
|
+
LogSanitizer.sanitize(@settings.except(:logger, :upsert_lock)).tap do |safe|
|
|
133
|
+
safe[:uncertain_write_delays].freeze
|
|
210
134
|
%i[url proxy].each do |key|
|
|
211
|
-
|
|
135
|
+
safe[key] = LogSanitizer.sanitize_url(@settings[key]) if @settings[key]
|
|
212
136
|
end
|
|
213
|
-
sanitized
|
|
214
137
|
end
|
|
138
|
+
end
|
|
215
139
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def validate_credentials!
|
|
220
|
-
raise Invalid, "url is required" if @raw_options[:url].nil? || @raw_options[:url].to_s.empty?
|
|
221
|
-
|
|
222
|
-
token_present = @raw_options[:api_token].present?
|
|
223
|
-
username_present = username.present?
|
|
224
|
-
password_present = @raw_options[:password].present?
|
|
225
|
-
if token_present && (username_present || password_present)
|
|
226
|
-
raise Invalid, "api_token cannot be combined with username/password"
|
|
227
|
-
end
|
|
228
|
-
return if token_present || (username_present && password_present)
|
|
140
|
+
def build_logger
|
|
141
|
+
source = @settings[:logger]
|
|
142
|
+
return if source.nil? && @settings[:log_level].nil?
|
|
229
143
|
|
|
230
|
-
|
|
144
|
+
source ||= ActiveSupport::Logger.new($stderr)
|
|
145
|
+
ActiveSupport::TaggedLogging.new(source).tap do |logger|
|
|
146
|
+
logger.level = @settings[:log_level] if @settings.key?(:log_level)
|
|
231
147
|
end
|
|
148
|
+
rescue ArgumentError, TypeError
|
|
149
|
+
raise Invalid, "log_level must be a Ruby Logger severity", cause: nil
|
|
150
|
+
end
|
|
232
151
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
raise Invalid, "uncertain_write_delays must contain 1 to 10 non-negative seconds totaling at most 60"
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
@raw_options[:uncertain_write_delays] = delays.freeze
|
|
242
|
-
rescue ArgumentError, TypeError
|
|
243
|
-
raise Invalid, "uncertain_write_delays must contain numeric seconds"
|
|
152
|
+
def validate_request!(method, params)
|
|
153
|
+
valid_method = method.is_a?(String) && method.valid_encoding? && method.ascii_only? &&
|
|
154
|
+
method.match?(/\A[a-z][a-z0-9_]*\.[a-z][a-zA-Z0-9_]*\z/)
|
|
155
|
+
unless valid_method
|
|
156
|
+
raise Invalid, "method must be a Zabbix API method name"
|
|
244
157
|
end
|
|
158
|
+
raise Invalid, "params must be a Hash or Array" unless params.is_a?(Hash) || params.is_a?(Array)
|
|
159
|
+
end
|
|
245
160
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
lock = @raw_options[:upsert_lock]
|
|
251
|
-
return if lock.nil? || lock.respond_to?(:call)
|
|
252
|
-
|
|
253
|
-
raise Invalid, "upsert_lock must respond to call(key, &block)"
|
|
161
|
+
def request_message(method, params)
|
|
162
|
+
request_id = @id_mutex.synchronize { @next_id += 1 }
|
|
163
|
+
{ method: method, params: params, id: request_id, jsonrpc: "2.0" }.tap do |message|
|
|
164
|
+
message[:auth] = @auth_token if @auth_token && authenticated_method?(method) && api_major_version < 7
|
|
254
165
|
end
|
|
166
|
+
end
|
|
255
167
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
match = @api_version.to_s.match(/\A(\d+)\.(\d+)\.(\d+)\z/)
|
|
261
|
-
supported = match && SUPPORTED_MAJOR_VERSIONS.cover?(match[1].to_i)
|
|
262
|
-
return if supported
|
|
263
|
-
|
|
264
|
-
message = "Zabbix API version #{@api_version.inspect} is not supported"
|
|
265
|
-
if @raw_options[:ignore_version]
|
|
266
|
-
log(:warn, "version.unsupported", version: @api_version)
|
|
267
|
-
else
|
|
268
|
-
raise ApiError, message
|
|
269
|
-
end
|
|
168
|
+
def parse_response(body, request_id)
|
|
169
|
+
response = JSON.parse(body)
|
|
170
|
+
unless response.is_a?(Hash) && response["jsonrpc"] == "2.0" && response["id"] == request_id
|
|
171
|
+
raise ProtocolError, "Invalid JSON-RPC response envelope or request ID"
|
|
270
172
|
end
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
# @return [void]
|
|
274
|
-
# @api private
|
|
275
|
-
def validate_http_auth_compatibility!
|
|
276
|
-
return unless api_major_version >= 7 && @raw_options[:http_user].present?
|
|
277
|
-
|
|
278
|
-
raise Invalid, "HTTP Basic authentication cannot be combined with Zabbix 7 Bearer authentication"
|
|
173
|
+
unless response.key?("result") ^ response.key?("error")
|
|
174
|
+
raise ProtocolError, "Invalid JSON-RPC response: expected exactly one result or error"
|
|
279
175
|
end
|
|
280
176
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return if @raw_options[:allow_insecure_http] == true
|
|
177
|
+
raise_api_error(response) if response.key?("error")
|
|
178
|
+
response.fetch("result")
|
|
179
|
+
rescue JSON::ParserError, TypeError
|
|
180
|
+
raise ProtocolError, "Invalid JSON response", cause: nil
|
|
181
|
+
end
|
|
287
182
|
|
|
288
|
-
|
|
183
|
+
def raise_api_error(response)
|
|
184
|
+
error = response["error"]
|
|
185
|
+
unless error.is_a?(Hash) && error["code"].is_a?(Integer) && error["message"].is_a?(String)
|
|
186
|
+
raise ProtocolError, "Invalid JSON-RPC error response"
|
|
289
187
|
end
|
|
290
188
|
|
|
291
|
-
#
|
|
292
|
-
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
@raw_options[:api_token].present? && @transport.uri.scheme == "http"
|
|
296
|
-
end
|
|
189
|
+
# 服务器文本可能回显任意请求值,包括宏及秘密。
|
|
190
|
+
safe_response = response.slice("jsonrpc", "id").merge("error" => error.slice("code"))
|
|
191
|
+
raise ApiError.new("Zabbix API error #{error.fetch("code")}", safe_response)
|
|
192
|
+
end
|
|
297
193
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
194
|
+
def login
|
|
195
|
+
parameter = api_major_version >= 6 ? :username : :user
|
|
196
|
+
api_request(method: "user.login", params: { parameter => @settings[:username], password: @settings[:password] })
|
|
197
|
+
end
|
|
302
198
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
199
|
+
def validate_api_version!
|
|
200
|
+
match = @api_version.is_a?(String) && @api_version.match(/\A(\d+)\.(\d+)\.(\d+)\z/)
|
|
201
|
+
raise ProtocolError, "Invalid API version response" unless match
|
|
202
|
+
return if SUPPORTED_MAJOR_VERSIONS.cover?(match[1].to_i)
|
|
203
|
+
raise ApiError, "Zabbix API version is not supported" unless @settings[:ignore_version]
|
|
307
204
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
@raw_options[:username] || @raw_options[:user]
|
|
311
|
-
end
|
|
205
|
+
log(:warn, "version.unsupported", version: @api_version)
|
|
206
|
+
end
|
|
312
207
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
authenticated_method?(method) && api_major_version < 7
|
|
316
|
-
end
|
|
208
|
+
def validate_http_auth_compatibility!
|
|
209
|
+
return unless api_major_version >= 7 && @settings[:http_user]
|
|
317
210
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
@auth_token if authenticated_method?(method) && api_major_version >= 7
|
|
321
|
-
end
|
|
211
|
+
raise Invalid, "HTTP Basic authentication cannot be combined with Zabbix 7 Bearer authentication"
|
|
212
|
+
end
|
|
322
213
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
!UNAUTHENTICATED_METHODS.include?(method.to_s)
|
|
326
|
-
end
|
|
214
|
+
def validate_token_transport!
|
|
215
|
+
return unless insecure_token_transport? && @settings[:allow_insecure_http] != true
|
|
327
216
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
body[key] || body[key.to_s]
|
|
331
|
-
end
|
|
217
|
+
raise Invalid, "api_token requires HTTPS unless allow_insecure_http is true"
|
|
218
|
+
end
|
|
332
219
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
error = safe_response["error"]
|
|
337
|
-
message = "Zabbix API error #{error["code"]}: #{error["message"]}"
|
|
338
|
-
message = "#{message} (#{error["data"]})" if error["data"]
|
|
339
|
-
raise ApiError.new("#{message}\nRequest:\n#{pretty_body(request_body)}", safe_response)
|
|
340
|
-
end
|
|
220
|
+
def insecure_token_transport?
|
|
221
|
+
@settings[:api_token] && @transport.uri.scheme == "http"
|
|
222
|
+
end
|
|
341
223
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return unless debug?
|
|
224
|
+
def insecure_https?
|
|
225
|
+
@transport.uri.scheme == "https" && @settings[:verify_ssl] != true
|
|
226
|
+
end
|
|
346
227
|
|
|
347
|
-
|
|
348
|
-
|
|
228
|
+
def api_major_version
|
|
229
|
+
@api_version.to_s.split(".", 2).first.to_i
|
|
230
|
+
end
|
|
349
231
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
232
|
+
def bearer_token_for(method)
|
|
233
|
+
@auth_token if authenticated_method?(method) && api_major_version >= 7
|
|
234
|
+
end
|
|
354
235
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
end
|
|
236
|
+
def authenticated_method?(method)
|
|
237
|
+
!UNAUTHENTICATED_METHODS.include?(method)
|
|
238
|
+
end
|
|
359
239
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
240
|
+
def monotonic_time
|
|
241
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def elapsed_ms(started_at)
|
|
245
|
+
((monotonic_time - started_at) * 1000).round(2) if started_at
|
|
246
|
+
end
|
|
364
247
|
end
|
|
365
248
|
end
|