zabbix_manager 5.1.5 → 6.0.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 +55 -9
- data/LICENSE +1 -1
- data/README.md +241 -24
- data/lib/zabbix_manager/classes/actions.rb +19 -17
- data/lib/zabbix_manager/classes/applications.rb +11 -22
- data/lib/zabbix_manager/classes/configurations.rb +20 -10
- data/lib/zabbix_manager/classes/discovery_rules.rb +23 -0
- data/lib/zabbix_manager/classes/errors.rb +18 -27
- data/lib/zabbix_manager/classes/events.rb +8 -3
- data/lib/zabbix_manager/classes/graphs.rb +48 -37
- data/lib/zabbix_manager/classes/host_groups.rb +64 -0
- data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
- data/lib/zabbix_manager/classes/hosts.rb +232 -76
- data/lib/zabbix_manager/classes/http_tests.rb +32 -0
- data/lib/zabbix_manager/classes/items.rb +206 -72
- data/lib/zabbix_manager/classes/maintenance.rb +8 -3
- data/lib/zabbix_manager/classes/media_types.rb +18 -0
- data/lib/zabbix_manager/classes/problems.rb +51 -62
- data/lib/zabbix_manager/classes/proxies.rb +45 -14
- data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
- data/lib/zabbix_manager/classes/roles.rb +46 -41
- data/lib/zabbix_manager/classes/screens.rb +36 -24
- data/lib/zabbix_manager/classes/scripts.rb +16 -12
- data/lib/zabbix_manager/classes/server.rb +8 -1
- data/lib/zabbix_manager/classes/templates.rb +26 -51
- data/lib/zabbix_manager/classes/triggers.rb +251 -67
- data/lib/zabbix_manager/classes/user_groups.rb +63 -0
- data/lib/zabbix_manager/classes/user_macros.rb +110 -0
- data/lib/zabbix_manager/classes/users.rb +16 -23
- data/lib/zabbix_manager/classes/value_maps.rb +52 -0
- data/lib/zabbix_manager/client.rb +183 -137
- data/lib/zabbix_manager/configuration.rb +107 -0
- data/lib/zabbix_manager/http_transport.rb +240 -0
- data/lib/zabbix_manager/log_sanitizer.rb +58 -0
- data/lib/zabbix_manager/monitoring/device.rb +393 -0
- data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
- data/lib/zabbix_manager/monitoring/line.rb +175 -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 +132 -0
- data/lib/zabbix_manager/monitoring/validation.rb +92 -0
- data/lib/zabbix_manager/monitoring.rb +265 -0
- data/lib/zabbix_manager/resource.rb +235 -0
- data/lib/zabbix_manager/traffic.rb +238 -0
- data/lib/zabbix_manager/version.rb +1 -1
- data/lib/zabbix_manager.rb +110 -145
- data/zabbix_manager.gemspec +12 -15
- metadata +67 -30
- data/lib/zabbix_manager/basic/basic_alias.rb +0 -20
- data/lib/zabbix_manager/basic/basic_func.rb +0 -84
- data/lib/zabbix_manager/basic/basic_init.rb +0 -35
- data/lib/zabbix_manager/basic/basic_logic.rb +0 -195
- data/lib/zabbix_manager/classes/drules.rb +0 -41
- data/lib/zabbix_manager/classes/hostgroups.rb +0 -20
- data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -29
- data/lib/zabbix_manager/classes/httptests.rb +0 -40
- data/lib/zabbix_manager/classes/mediatypes.rb +0 -79
- data/lib/zabbix_manager/classes/unusable.rb +0 -11
- data/lib/zabbix_manager/classes/usergroups.rb +0 -53
- data/lib/zabbix_manager/classes/usermacros.rb +0 -136
- data/lib/zabbix_manager/classes/valuemaps.rb +0 -32
|
@@ -1,123 +1,279 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class ZabbixManager
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
# 主机身份解析、状态对账与模板关联;技术名称和显示名称分别处理。
|
|
5
|
+
class Hosts < Resource
|
|
6
|
+
# 返回主机对应的 Zabbix API 模块名。
|
|
6
7
|
def method_name
|
|
7
8
|
"host"
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
#
|
|
11
|
+
# 使用技术主机名作为通用 CRUD 标识。
|
|
11
12
|
def identify
|
|
12
13
|
"host"
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
#
|
|
16
|
+
# 返回创建主机时的克制默认值。
|
|
16
17
|
def default_options
|
|
17
18
|
{
|
|
18
|
-
|
|
19
|
-
interfaces: {
|
|
20
|
-
main: 1,
|
|
21
|
-
useip: 1,
|
|
22
|
-
type: 2,
|
|
23
|
-
ip: nil,
|
|
24
|
-
dns: "",
|
|
25
|
-
port: 161,
|
|
26
|
-
details: {
|
|
27
|
-
version: 2,
|
|
28
|
-
community: "public"
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
status: 0,
|
|
32
|
-
available: 1,
|
|
33
|
-
groups: [],
|
|
34
|
-
proxy_hostid: nil,
|
|
19
|
+
status: 0,
|
|
35
20
|
inventory_mode: 1
|
|
36
21
|
}
|
|
37
22
|
end
|
|
38
23
|
|
|
39
|
-
#
|
|
24
|
+
# 按主机 ID 查询完整主机及接口、群组和模板。
|
|
40
25
|
def dump_by_id(data)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
@client.api_request(
|
|
27
|
+
method: "host.get",
|
|
28
|
+
params: {
|
|
29
|
+
filter: { key.to_sym => data[key.to_sym] },
|
|
30
|
+
output: "extend",
|
|
31
|
+
selectGroups: "extend",
|
|
32
|
+
selectInterfaces: "extend",
|
|
33
|
+
selectParentTemplates: ["templateid", "host", "name"]
|
|
34
|
+
}
|
|
50
35
|
)
|
|
51
36
|
end
|
|
52
37
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
# 向指定主机追加模板关联,保留现有模板。
|
|
39
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
40
|
+
def link_templates(host_ids:, template_ids:)
|
|
41
|
+
write_templates("massadd", host_ids, template_ids)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 完整替换指定主机的模板关联,空模板集合明确清除全部关联。
|
|
45
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
46
|
+
def replace_templates(host_ids:, template_ids:)
|
|
47
|
+
write_templates("massupdate", host_ids, template_ids, allow_empty: true)
|
|
48
|
+
end
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
result
|
|
50
|
+
# 从指定主机解除选定模板的关联,保留模板生成的实体。
|
|
51
|
+
# @return [Array<Integer>] 已更新主机 ID
|
|
52
|
+
def unlink_templates(host_ids:, template_ids:)
|
|
53
|
+
ids = normalized_ids(host_ids)
|
|
54
|
+
templates = normalized_ids(template_ids)
|
|
55
|
+
result = @client.api_request(method: "host.massremove", params: { hostids: ids, templateids: templates })
|
|
56
|
+
response_ids(result, expected: ids)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 使用显式群组和接口创建主机,不注入隐藏凭据。
|
|
60
|
+
def create(data)
|
|
61
|
+
attributes = default_options.merge(data.deep_symbolize_keys)
|
|
62
|
+
validate_create_attributes!(attributes)
|
|
63
|
+
result = @client.api_request(method: "host.create", params: attributes)
|
|
64
|
+
response_id(result)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 规范化并校验设备定义,不执行远端查询或写入。
|
|
68
|
+
# 已存在设备允许只更新部分字段,新设备的群组和接口由 create 再次严格校验。
|
|
69
|
+
# @return [Hash]
|
|
70
|
+
def validate(data)
|
|
71
|
+
attributes = data.deep_symbolize_keys
|
|
72
|
+
raise Invalid, "host is required" if attributes[:host].blank?
|
|
73
|
+
|
|
74
|
+
host_interfaces.validate(attributes[:interfaces]) if attributes.key?(:interfaces)
|
|
75
|
+
attributes
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# 幂等同步主机元数据和显式接口,调用方保持业务字段权威。
|
|
79
|
+
def reconcile(data)
|
|
80
|
+
attributes = validate(data)
|
|
81
|
+
host = attributes.fetch(:host)
|
|
82
|
+
|
|
83
|
+
hostid = get_id(host: host)
|
|
84
|
+
unless hostid
|
|
85
|
+
outcome, hostid = @client.with_upsert_lock("host-create:#{host}") do
|
|
86
|
+
current_id = get_id(host: host)
|
|
87
|
+
current_id ? [:existing, current_id] : [:created, create(attributes)]
|
|
88
|
+
end
|
|
89
|
+
return hostid.to_i if outcome == :created
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
update_existing(hostid, attributes)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# 更新主机元数据,并把接口同步委托给带主机锁的接口模块。
|
|
96
|
+
# @return [Integer]
|
|
97
|
+
# @api private
|
|
98
|
+
private def update_existing(hostid, attributes)
|
|
99
|
+
attributes = attributes.dup
|
|
100
|
+
|
|
101
|
+
interfaces = Array.wrap(attributes.delete(:interfaces))
|
|
102
|
+
host_interfaces.validate(interfaces)
|
|
103
|
+
update_attributes = attributes.merge(hostid: hostid)
|
|
104
|
+
host_interfaces.reconcile_for_host(hostid: hostid, interfaces: interfaces) if interfaces.any?
|
|
105
|
+
result = @client.api_request(method: "host.update", params: update_attributes)
|
|
106
|
+
response_id(result, expected: [hostid])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# 查询指定主机的全部接口。
|
|
110
|
+
def interfaces_for(hostid)
|
|
111
|
+
host_interfaces.for_host(hostid)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# 返回主机的第一个接口 ID;高频业务应优先使用 host_interfaces。
|
|
115
|
+
def get_interface_id(hostid)
|
|
116
|
+
interfaces_for(hostid).first&.fetch("interfaceid", nil)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# 按技术主机名查询主机 ID。
|
|
120
|
+
def get_host_id(name)
|
|
121
|
+
find_host_id(host: name)
|
|
63
122
|
end
|
|
64
123
|
|
|
65
|
-
#
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
124
|
+
# 按可见名称查询主机 ID。
|
|
125
|
+
def get_hostid_by_name(name)
|
|
126
|
+
find_host_id(name: name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# 用技术名、可见名或接口 IP/DNS 解析唯一主机。
|
|
130
|
+
def find_by_candidates(candidates)
|
|
131
|
+
matches = Array(candidates).filter_map { |candidate| candidate.to_s.strip.presence }.uniq.flat_map do |candidate|
|
|
132
|
+
hosts = [find_host_by_filter(host: candidate), find_host_by_filter(name: candidate)].compact
|
|
133
|
+
interface = find_interface_by_endpoint(candidate)
|
|
134
|
+
hosts << find_host_by_filter(hostid: interface["hostid"]) if interface
|
|
135
|
+
hosts
|
|
136
|
+
end
|
|
137
|
+
matches.uniq! { |host| host.fetch("hostid") }
|
|
138
|
+
if matches.length > 1
|
|
139
|
+
raise Conflict, "host candidates resolve to multiple hosts; provide an explicit host object"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
matches.first
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# 按主机 ID 查询唯一主机,并返回表达式需要的技术名称。
|
|
146
|
+
# @return [Hash, nil]
|
|
147
|
+
def find_by_id(hostid)
|
|
148
|
+
find_host_by_filter(hostid: hostid)
|
|
149
|
+
end
|
|
69
150
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
151
|
+
# 解析供流量查询和监控表达式使用的唯一主机;只执行读取。
|
|
152
|
+
# 显式 ID 与技术名必须同时匹配,候选名称不能分别指向不同主机。
|
|
153
|
+
# @param reference [String, Array<String>, Hash] 名称/IP/DNS 候选,或同时包含 hostid 和 host 的 Hash
|
|
154
|
+
# @return [Hash] 符号键主机属性,包含 hostid 和技术名 host
|
|
155
|
+
# @raise [Invalid] 本地主机引用无效
|
|
156
|
+
# @raise [ApiError] 主机不存在或当前 API 用户不可见
|
|
157
|
+
# @raise [Conflict] 候选不唯一,或 ID 与技术名不匹配
|
|
158
|
+
# @raise [ProtocolError] API 返回无效的主机身份
|
|
159
|
+
def resolve(reference)
|
|
160
|
+
if reference.is_a?(Hash)
|
|
161
|
+
expected = Monitoring::Validation.hash!(reference, "host")
|
|
162
|
+
Monitoring::Validation.host!(expected)
|
|
163
|
+
found = find_by_id(expected[:hostid])
|
|
164
|
+
else
|
|
165
|
+
candidates = Array.wrap(reference).map do |candidate|
|
|
166
|
+
Monitoring::Validation.text!(candidate, "host candidate").strip
|
|
167
|
+
end
|
|
168
|
+
raise Invalid, "host candidates are required" if candidates.empty?
|
|
74
169
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
raise ZbxError, "#{data.inspect} get_interfaceid 查无记录" unless interface_id
|
|
170
|
+
found = find_by_candidates(candidates.uniq)
|
|
171
|
+
end
|
|
172
|
+
raise ApiError, "Zabbix host was not found for the supplied reference" unless found
|
|
79
173
|
|
|
80
|
-
|
|
81
|
-
|
|
174
|
+
resolved = resolved_host(found)
|
|
175
|
+
if expected && (resolved[:hostid].to_s != expected[:hostid].to_s || resolved[:host] != expected[:host])
|
|
176
|
+
raise Conflict, "hostid and technical host name do not refer to the same Zabbix host"
|
|
177
|
+
end
|
|
82
178
|
|
|
83
|
-
|
|
84
|
-
|
|
179
|
+
resolved
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 批量启用或停用主机。
|
|
183
|
+
# @return [Array<Integer>]
|
|
184
|
+
def set_status(hostids, enabled:)
|
|
185
|
+
validate_boolean!(enabled, "enabled")
|
|
186
|
+
ids = normalized_ids(hostids)
|
|
187
|
+
|
|
188
|
+
params = ids.map { |hostid| { hostid: hostid, status: enabled ? 0 : 1 } }
|
|
189
|
+
result = @client.api_request(method: "host.update", params: params)
|
|
190
|
+
response_ids(result, expected: ids)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
85
194
|
|
|
86
|
-
|
|
87
|
-
|
|
195
|
+
# 远端身份损坏是协议错误,不能当作调用方输入错误或不存在处理。
|
|
196
|
+
def resolved_host(found)
|
|
197
|
+
resolved = Monitoring::Validation.hash!(found, "Zabbix host")
|
|
198
|
+
Monitoring::Validation.host!(resolved)
|
|
199
|
+
resolved
|
|
200
|
+
rescue Invalid
|
|
201
|
+
raise ProtocolError, "invalid host response: hostid and technical host are required", cause: nil
|
|
202
|
+
end
|
|
88
203
|
|
|
89
|
-
|
|
90
|
-
|
|
204
|
+
def write_templates(operation, host_ids, template_ids, allow_empty: false)
|
|
205
|
+
ids = normalized_ids(host_ids)
|
|
206
|
+
templates = normalized_ids(template_ids, allow_empty: allow_empty)
|
|
207
|
+
result = @client.api_request(
|
|
208
|
+
method: "host.#{operation}",
|
|
209
|
+
params: {
|
|
210
|
+
hosts: ids.map { |id| { hostid: id } },
|
|
211
|
+
templates: templates.map { |id| { templateid: id } }
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
response_ids(result, expected: ids)
|
|
91
215
|
end
|
|
92
216
|
|
|
93
|
-
#
|
|
94
|
-
def
|
|
95
|
-
|
|
217
|
+
# 校验创建主机所需的技术名、群组和接口。
|
|
218
|
+
def validate_create_attributes!(attributes)
|
|
219
|
+
raise Invalid, "host is required" if attributes[:host].blank?
|
|
220
|
+
raise Invalid, "groups are required when creating a host" if Array.wrap(attributes[:groups]).empty?
|
|
221
|
+
raise Invalid, "interfaces are required when creating a host" if Array.wrap(attributes[:interfaces]).empty?
|
|
222
|
+
|
|
223
|
+
attributes[:interfaces] = host_interfaces.validate_for_create(attributes[:interfaces])
|
|
224
|
+
end
|
|
96
225
|
|
|
226
|
+
# 使用精确过滤查询唯一主机。
|
|
227
|
+
def find_host_by_filter(filter)
|
|
97
228
|
result = @client.api_request(
|
|
98
|
-
method: "
|
|
99
|
-
params: {
|
|
229
|
+
method: "host.get",
|
|
230
|
+
params: {
|
|
231
|
+
output: %w[hostid host name status],
|
|
232
|
+
filter: filter,
|
|
233
|
+
selectInterfaces: %w[interfaceid ip dns]
|
|
234
|
+
}
|
|
100
235
|
)
|
|
236
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
101
237
|
|
|
102
|
-
|
|
103
|
-
|
|
238
|
+
response_identifier(result.first["hostid"]) if result.first
|
|
239
|
+
result.first
|
|
240
|
+
end
|
|
104
241
|
|
|
105
|
-
|
|
106
|
-
|
|
242
|
+
# 按 IP 和 DNS 端点查询唯一主机接口。
|
|
243
|
+
def find_interface_by_endpoint(candidate)
|
|
244
|
+
matches = []
|
|
245
|
+
%i[ip dns].each do |field|
|
|
246
|
+
result = @client.api_request(
|
|
247
|
+
method: "hostinterface.get",
|
|
248
|
+
params: { filter: { field => candidate }, output: ["hostid"] }
|
|
249
|
+
)
|
|
250
|
+
matches.concat(result)
|
|
251
|
+
end
|
|
252
|
+
matches.uniq! { |interface| interface.fetch("hostid") }
|
|
253
|
+
raise Conflict, "interface endpoint #{candidate} belongs to multiple hosts" if matches.length > 1
|
|
107
254
|
|
|
108
|
-
|
|
255
|
+
matches.first
|
|
109
256
|
end
|
|
110
257
|
|
|
111
|
-
#
|
|
112
|
-
def
|
|
258
|
+
# 只查询唯一主机 ID。
|
|
259
|
+
def find_host_id(filter)
|
|
113
260
|
result = @client.api_request(
|
|
114
|
-
method: "host.
|
|
261
|
+
method: "host.get",
|
|
115
262
|
params: {
|
|
116
|
-
|
|
117
|
-
|
|
263
|
+
output: ["hostid"],
|
|
264
|
+
filter: filter
|
|
118
265
|
}
|
|
119
266
|
)
|
|
120
|
-
|
|
267
|
+
raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
|
|
268
|
+
|
|
269
|
+
response_identifier(result.first["hostid"]).to_s if result.first
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# 延迟构建接口模块并复用当前客户端。
|
|
273
|
+
# @return [HostInterfaces]
|
|
274
|
+
# @api private
|
|
275
|
+
def host_interfaces
|
|
276
|
+
@host_interfaces ||= HostInterfaces.new(@client)
|
|
121
277
|
end
|
|
122
278
|
end
|
|
123
279
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ZabbixManager
|
|
4
|
+
# Web 场景以 hostid + name 定位;步骤定义及执行凭据由调用方提供。
|
|
5
|
+
class HttpTests < Resource
|
|
6
|
+
# 返回 Web 场景对象对应的 Zabbix API 方法前缀。
|
|
7
|
+
#
|
|
8
|
+
# @return [String]
|
|
9
|
+
def method_name
|
|
10
|
+
"httptest"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 返回创建 Web 场景时使用的默认步骤列表。
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash] Web 场景默认属性
|
|
16
|
+
def default_options
|
|
17
|
+
{
|
|
18
|
+
steps: []
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 生成由 Web 场景名称和所属主机构成的稳定查询条件。
|
|
23
|
+
#
|
|
24
|
+
# @param data [Hash] 包含 name 和 hostid 的 Web 场景属性
|
|
25
|
+
# @raise [KeyError] 缺少 name 或 hostid 时抛出
|
|
26
|
+
# @return [Hash] Web 场景唯一查询条件
|
|
27
|
+
def identity_filter(data)
|
|
28
|
+
attributes = data.deep_symbolize_keys
|
|
29
|
+
{ name: attributes.fetch(:name), hostid: attributes.fetch(:hostid) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|