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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -9
  3. data/LICENSE +1 -1
  4. data/README.md +241 -24
  5. data/lib/zabbix_manager/classes/actions.rb +19 -17
  6. data/lib/zabbix_manager/classes/applications.rb +11 -22
  7. data/lib/zabbix_manager/classes/configurations.rb +20 -10
  8. data/lib/zabbix_manager/classes/discovery_rules.rb +23 -0
  9. data/lib/zabbix_manager/classes/errors.rb +18 -27
  10. data/lib/zabbix_manager/classes/events.rb +8 -3
  11. data/lib/zabbix_manager/classes/graphs.rb +48 -37
  12. data/lib/zabbix_manager/classes/host_groups.rb +64 -0
  13. data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
  14. data/lib/zabbix_manager/classes/hosts.rb +232 -76
  15. data/lib/zabbix_manager/classes/http_tests.rb +32 -0
  16. data/lib/zabbix_manager/classes/items.rb +206 -72
  17. data/lib/zabbix_manager/classes/maintenance.rb +8 -3
  18. data/lib/zabbix_manager/classes/media_types.rb +18 -0
  19. data/lib/zabbix_manager/classes/problems.rb +51 -62
  20. data/lib/zabbix_manager/classes/proxies.rb +45 -14
  21. data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
  22. data/lib/zabbix_manager/classes/roles.rb +46 -41
  23. data/lib/zabbix_manager/classes/screens.rb +36 -24
  24. data/lib/zabbix_manager/classes/scripts.rb +16 -12
  25. data/lib/zabbix_manager/classes/server.rb +8 -1
  26. data/lib/zabbix_manager/classes/templates.rb +26 -51
  27. data/lib/zabbix_manager/classes/triggers.rb +251 -67
  28. data/lib/zabbix_manager/classes/user_groups.rb +63 -0
  29. data/lib/zabbix_manager/classes/user_macros.rb +110 -0
  30. data/lib/zabbix_manager/classes/users.rb +16 -23
  31. data/lib/zabbix_manager/classes/value_maps.rb +52 -0
  32. data/lib/zabbix_manager/client.rb +183 -137
  33. data/lib/zabbix_manager/configuration.rb +107 -0
  34. data/lib/zabbix_manager/http_transport.rb +240 -0
  35. data/lib/zabbix_manager/log_sanitizer.rb +58 -0
  36. data/lib/zabbix_manager/monitoring/device.rb +393 -0
  37. data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
  38. data/lib/zabbix_manager/monitoring/line.rb +175 -0
  39. data/lib/zabbix_manager/monitoring/line_items.rb +110 -0
  40. data/lib/zabbix_manager/monitoring/line_lifecycle.rb +169 -0
  41. data/lib/zabbix_manager/monitoring/line_plan.rb +113 -0
  42. data/lib/zabbix_manager/monitoring/thresholds.rb +182 -0
  43. data/lib/zabbix_manager/monitoring/traffic_items.rb +132 -0
  44. data/lib/zabbix_manager/monitoring/validation.rb +92 -0
  45. data/lib/zabbix_manager/monitoring.rb +265 -0
  46. data/lib/zabbix_manager/resource.rb +235 -0
  47. data/lib/zabbix_manager/traffic.rb +238 -0
  48. data/lib/zabbix_manager/version.rb +1 -1
  49. data/lib/zabbix_manager.rb +110 -145
  50. data/zabbix_manager.gemspec +12 -15
  51. metadata +67 -30
  52. data/lib/zabbix_manager/basic/basic_alias.rb +0 -20
  53. data/lib/zabbix_manager/basic/basic_func.rb +0 -84
  54. data/lib/zabbix_manager/basic/basic_init.rb +0 -35
  55. data/lib/zabbix_manager/basic/basic_logic.rb +0 -195
  56. data/lib/zabbix_manager/classes/drules.rb +0 -41
  57. data/lib/zabbix_manager/classes/hostgroups.rb +0 -20
  58. data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -29
  59. data/lib/zabbix_manager/classes/httptests.rb +0 -40
  60. data/lib/zabbix_manager/classes/mediatypes.rb +0 -79
  61. data/lib/zabbix_manager/classes/unusable.rb +0 -11
  62. data/lib/zabbix_manager/classes/usergroups.rb +0 -53
  63. data/lib/zabbix_manager/classes/usermacros.rb +0 -136
  64. 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
- class Hosts < Basic
5
- # 与 Zabbix API 交互的主机方法名称
4
+ # 主机身份解析、状态对账与模板关联;技术名称和显示名称分别处理。
5
+ class Hosts < Resource
6
+ # 返回主机对应的 Zabbix API 模块名。
6
7
  def method_name
7
8
  "host"
8
9
  end
9
10
 
10
- # 用于通过 Zabbix API 标识特定主机对象的 id 字段名称
11
+ # 使用技术主机名作为通用 CRUD 标识。
11
12
  def identify
12
13
  "host"
13
14
  end
14
15
 
15
- # 在通过 Zabbix API 创建主机对象时使用的默认选项
16
+ # 返回创建主机时的克制默认值。
16
17
  def default_options
17
18
  {
18
- host: nil,
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
- # 通过 Zabbix API 从指定的 key 值获取主机对象数据
24
+ # 按主机 ID 查询完整主机及接口、群组和模板。
40
25
  def dump_by_id(data)
41
- log "[DEBUG] 调用 dump_by_id 方法,参数: #{data.inspect}"
42
-
43
- get_raw({
44
- filter: {
45
- key.to_sym => data[key.to_sym]
46
- },
47
- output: "extend",
48
- selectHosts: "shorten"
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
- # 通过 Zabbix API 根据名称获取 Zabbix 对象的 ID
54
- def get_ids_by_name(data)
55
- log "[DEBUG] 调用 #{method_name}.get_ids_by_name 方法,参数: #{data.inspect}"
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
- result = get_raw({
58
- filter: { name: [data].flatten },
59
- output: [key]
60
- }
61
- )
62
- result.empty? ? nil : result.map { |i| { key.to_sym => i[key] } }
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
- # 使用 Zabbix API 创建或更新 Zabbix 对象
66
- def create_or_update(data)
67
- # 输出调试日志,显示方法调用和参数信息
68
- log "[DEBUG] 调用 #{method_name}.create_or_update 方法,参数: #{data.inspect}"
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
- # 获取 Zabbix 对象的 ID
71
- hostid = get_id(identify.to_sym => data[identify.to_sym])
72
- # 如果不存在对应的对象ID,则创建新对象
73
- return create(data) unless hostid
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
- # 获取对象的接口 ID
76
- interface_id = get_interfaceid(hostid)
77
- # 如果接口 ID 不存在,抛出异常
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
- dump_data = data.dup
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
- # 从数据中提取并删除 interfaces,并合并接口 ID
84
- interfaces = dump_data.delete(:interfaces)&.merge(interfaceid: interface_id)
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
- # 构建参数,包括 interfaces 和 Zabbix 对象的键值对
87
- params = dump_data.merge(interfaces: interfaces).merge(key.to_sym => hostid.to_s)
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
- update(params)
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
- # 从 API 中基于提供的 hostid 获取 Zabbix interface 的 id
94
- def get_interfaceid(hostid)
95
- log "[DEBUG] 调用 #{method_name}.get_interfaceid 方法,参数: #{hostid.inspect}"
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: "hostinterface.get",
99
- params: { output: "interfaceid", hostids: hostid }
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
- # 使用 dig 方法获取深层嵌套的值
103
- interface_id = result&.dig(0, "interfaceid")&.to_i
238
+ response_identifier(result.first["hostid"]) if result.first
239
+ result.first
240
+ end
104
241
 
105
- # 如果获取接口 ID 失败,提供更具体的错误信息
106
- raise ZbxError, "无法获取 #{hostid} 的接口 ID" if interface_id.nil?
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
- interface_id
255
+ matches.first
109
256
  end
110
257
 
111
- # 在 Hosts 中使用 Zabbix API 取消关联/移除模板
112
- def unlink_templates(data)
258
+ # 只查询唯一主机 ID。
259
+ def find_host_id(filter)
113
260
  result = @client.api_request(
114
- method: "host.massRemove",
261
+ method: "host.get",
115
262
  params: {
116
- hostids: data[:hosts_id],
117
- templates: data[:templates_id]
263
+ output: ["hostid"],
264
+ filter: filter
118
265
  }
119
266
  )
120
- result.empty? ? false : true
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