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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/README.md +73 -45
  4. data/lib/zabbix_manager/classes/actions.rb +3 -11
  5. data/lib/zabbix_manager/classes/applications.rb +2 -8
  6. data/lib/zabbix_manager/classes/configurations.rb +4 -9
  7. data/lib/zabbix_manager/classes/{drules.rb → discovery_rules.rb} +2 -8
  8. data/lib/zabbix_manager/classes/errors.rb +3 -0
  9. data/lib/zabbix_manager/classes/events.rb +2 -1
  10. data/lib/zabbix_manager/classes/graphs.rb +19 -15
  11. data/lib/zabbix_manager/classes/{hostgroups.rb → host_groups.rb} +5 -11
  12. data/lib/zabbix_manager/classes/host_interfaces.rb +261 -0
  13. data/lib/zabbix_manager/classes/hosts.rb +139 -89
  14. data/lib/zabbix_manager/classes/{httptests.rb → http_tests.rb} +2 -8
  15. data/lib/zabbix_manager/classes/items.rb +139 -92
  16. data/lib/zabbix_manager/classes/maintenance.rb +2 -1
  17. data/lib/zabbix_manager/classes/{mediatypes.rb → media_types.rb} +2 -7
  18. data/lib/zabbix_manager/classes/problems.rb +11 -40
  19. data/lib/zabbix_manager/classes/proxies.rb +7 -15
  20. data/lib/zabbix_manager/classes/proxy_groups.rb +16 -0
  21. data/lib/zabbix_manager/classes/roles.rb +13 -19
  22. data/lib/zabbix_manager/classes/screens.rb +11 -30
  23. data/lib/zabbix_manager/classes/scripts.rb +2 -8
  24. data/lib/zabbix_manager/classes/server.rb +1 -0
  25. data/lib/zabbix_manager/classes/templates.rb +2 -65
  26. data/lib/zabbix_manager/classes/triggers.rb +62 -53
  27. data/lib/zabbix_manager/classes/user_groups.rb +63 -0
  28. data/lib/zabbix_manager/classes/user_macros.rb +111 -0
  29. data/lib/zabbix_manager/classes/users.rb +15 -54
  30. data/lib/zabbix_manager/classes/value_maps.rb +52 -0
  31. data/lib/zabbix_manager/client.rb +162 -279
  32. data/lib/zabbix_manager/configuration.rb +115 -0
  33. data/lib/zabbix_manager/http_transport.rb +145 -97
  34. data/lib/zabbix_manager/log_sanitizer.rb +18 -29
  35. data/lib/zabbix_manager/monitoring/device.rb +394 -0
  36. data/lib/zabbix_manager/monitoring/expressions.rb +36 -0
  37. data/lib/zabbix_manager/monitoring/line.rb +176 -0
  38. data/lib/zabbix_manager/monitoring/line_items.rb +110 -0
  39. data/lib/zabbix_manager/monitoring/line_lifecycle.rb +169 -0
  40. data/lib/zabbix_manager/monitoring/line_plan.rb +113 -0
  41. data/lib/zabbix_manager/monitoring/thresholds.rb +182 -0
  42. data/lib/zabbix_manager/monitoring/traffic_items.rb +133 -0
  43. data/lib/zabbix_manager/monitoring/validation.rb +92 -0
  44. data/lib/zabbix_manager/monitoring.rb +216 -642
  45. data/lib/zabbix_manager/resource.rb +262 -0
  46. data/lib/zabbix_manager/traffic.rb +239 -0
  47. data/lib/zabbix_manager/version.rb +1 -1
  48. data/lib/zabbix_manager.rb +66 -179
  49. data/zabbix_manager.gemspec +6 -22
  50. metadata +41 -190
  51. data/lib/zabbix_manager/basic/basic_alias.rb +0 -42
  52. data/lib/zabbix_manager/basic/basic_func.rb +0 -55
  53. data/lib/zabbix_manager/basic/basic_init.rb +0 -50
  54. data/lib/zabbix_manager/basic/basic_logic.rb +0 -239
  55. data/lib/zabbix_manager/classes/hostinterfaces.rb +0 -189
  56. data/lib/zabbix_manager/classes/usergroups.rb +0 -75
  57. data/lib/zabbix_manager/classes/usermacros.rb +0 -222
  58. data/lib/zabbix_manager/classes/valuemaps.rb +0 -26
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ZabbixManager
4
- class Scripts < Basic
4
+ # 远端脚本执行与可用脚本查询;执行请求可能产生 API 之外的设备副作用。
5
+ class Scripts < Resource
5
6
  # 返回 Zabbix API 中脚本对象的方法名前缀。
6
7
  #
7
8
  # @return [String] 脚本对象的方法名前缀
@@ -9,13 +10,6 @@ class ZabbixManager
9
10
  "script"
10
11
  end
11
12
 
12
- # 返回用于唯一识别脚本的业务字段名。
13
- #
14
- # @return [String] 脚本对象的业务标识字段名
15
- def identify
16
- "name"
17
- end
18
-
19
13
  # 在指定主机上执行 Zabbix 脚本。
20
14
  #
21
15
  # 示例:
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ZabbixManager
4
+ # 已连接服务器的版本快照;复用 Client 初始化结果,不发起额外版本查询。
4
5
  class Server
5
6
  # 返回连接对应的 Zabbix API 版本。
6
7
  #
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ZabbixManager
4
- class Templates < Basic
4
+ # 按模板技术名称解析 ID;返回引用供主机或模板关联操作使用。
5
+ class Templates < Resource
5
6
  # 返回 Zabbix API 中模板对象的方法名前缀。
6
7
  #
7
8
  # @return [String] 模板对象的方法名前缀
@@ -16,17 +17,6 @@ class ZabbixManager
16
17
  "host"
17
18
  end
18
19
 
19
- # 删除指定模板并返回首个已删除模板的 ID。
20
- #
21
- # @param data [Array] 要删除的 templateid 数组
22
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
23
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
24
- # @return [Integer, nil] 已删除模板的 ID,无结果时返回 nil
25
- def delete(data)
26
- result = @client.api_request(method: "template.delete", params: [data])
27
- result.empty? ? nil : result["templateids"][0].to_i
28
- end
29
-
30
20
  # 根据查询条件获取模板 ID 列表。
31
21
  #
32
22
  # @param data [Hash] Zabbix template.get 查询参数
@@ -39,59 +29,6 @@ class ZabbixManager
39
29
  end
40
30
  end
41
31
 
42
- # 批量更新主机与模板的关联关系。
43
- #
44
- # @param data [Hash] 包含 hosts_id 和 templates_id 数组
45
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
46
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
47
- # @return [Boolean] API 是否返回有效结果
48
- def mass_update(data)
49
- result = @client.api_request(
50
- method: "template.massUpdate",
51
- params: {
52
- hosts: data[:hosts_id].map { |t| { hostid: t } },
53
- templates: data[:templates_id].map { |t| { templateid: t } }
54
- }
55
- )
56
- result.empty? ? false : true
57
- end
58
-
59
- # 批量为主机添加模板关联。
60
- #
61
- # @param data [Hash] 包含 hosts_id 和 templates_id 数组
62
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
63
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
64
- # @return [Boolean] API 是否返回有效结果
65
- def mass_add(data)
66
- result = @client.api_request(
67
- method: "template.massAdd",
68
- params: {
69
- hosts: data[:hosts_id].map { |t| { hostid: t } },
70
- templates: data[:templates_id].map { |t| { templateid: t } }
71
- }
72
- )
73
- result.empty? ? false : true
74
- end
75
-
76
- # 批量移除主机的模板或主机组关联。
77
- #
78
- # @param data [Hash] 包含 hosts_id、templates_id 及可选 group_id
79
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
80
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
81
- # @return [Boolean] API 是否返回有效结果
82
- def mass_remove(data)
83
- result = @client.api_request(
84
- method: "template.massRemove",
85
- params: {
86
- hostids: data[:hosts_id],
87
- templateids: data[:templates_id],
88
- groupids: data[:group_id],
89
- force: 1
90
- }
91
- )
92
- result.empty? ? false : true
93
- end
94
-
95
32
  # 按模板技术名称查询,并返回可直接用于主机或模板更新的引用对象。
96
33
  #
97
34
  # @param data [String, Array<String>] 一个或多个模板技术名称
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ZabbixManager
4
- class Triggers < Basic
4
+ # 触发器与依赖对账;受管身份使用标签,创建结果不明时只回读、不重放。
5
+ class Triggers < Resource
5
6
  DEFAULT_UNCERTAIN_WRITE_DELAYS = [0, 0.25, 1, 2].freeze
7
+ MAX_MANAGED_KEY_LENGTH = 200
6
8
  # 返回触发器对应的 Zabbix API 模块名。
7
9
  #
8
10
  # @return [String]
@@ -19,13 +21,11 @@ class ZabbixManager
19
21
 
20
22
  # 按触发器 ID 查询完整对象及关联项和函数。
21
23
  #
22
- # @param data [Hash] Should include desired object's key and value
23
- # @raise [ApiError] Error returned when there is a problem with the Zabbix API call.
24
- # @raise [TransportError] Error raised when HTTP status from Zabbix Server response is not a 200 OK.
25
- # @return [Hash]
24
+ # @param data [Hash] 包含目标触发器 ID 字段及其值
25
+ # @raise [ApiError] Zabbix API 明确返回业务错误
26
+ # @raise [TransportError] HTTP 或网络请求失败,可能无法确认远端结果
27
+ # @return [Array<Hash>] 匹配的触发器及其关联对象
26
28
  def dump_by_id(data)
27
- log "[DEBUG] Call dump_by_id with parameters: #{data.inspect}"
28
-
29
29
  @client.api_request(
30
30
  method: "trigger.get",
31
31
  params: {
@@ -43,7 +43,7 @@ class ZabbixManager
43
43
  # @return [Array<Hash>]
44
44
  def for_host(hostid, output: "extend", select_items: nil, select_functions: nil,
45
45
  select_dependencies: nil, select_tags: nil, filter: nil)
46
- raise Invalid, "hostid is required" if hostid.blank?
46
+ positive_id_attribute(hostid, "hostid")
47
47
 
48
48
  params = { hostids: hostid, output: output }
49
49
  params[:selectItems] = select_items if select_items
@@ -51,12 +51,13 @@ class ZabbixManager
51
51
  params[:selectDependencies] = select_dependencies if select_dependencies
52
52
  params[:selectTags] = select_tags if select_tags
53
53
  params[:filter] = filter if filter.present?
54
- @client.api_request(method: "trigger.get", params: params)
54
+ response_objects(@client.api_request(method: "trigger.get", params: params))
55
55
  end
56
56
 
57
57
  # 按主机、描述和可选标签查询唯一触发器。
58
58
  # @return [Hash, nil]
59
59
  def find_for_host(hostid:, description:, tags: nil)
60
+ positive_id_attribute(hostid, "hostid")
60
61
  params = {
61
62
  hostids: hostid,
62
63
  filter: { description: description },
@@ -68,23 +69,33 @@ class ZabbixManager
68
69
  method: "trigger.get",
69
70
  params: params
70
71
  )
72
+ response_objects(result)
73
+ result.each { |trigger| response_identifier(trigger["triggerid"]) }
71
74
  raise Conflict, "multiple triggers match #{description} on host #{hostid}" if result.length > 1
72
75
 
73
76
  result.first
74
77
  end
75
78
 
76
79
  # 按管理标签查询唯一触发器。
80
+ # 展开表达式并读取依赖,供不确定写入后的完整期望状态核对。
81
+ # @param hostid [Integer, String] 触发器所属主机
82
+ # @param managed_key [String] 稳定、非秘密的受管身份
77
83
  # @return [Hash, nil]
78
84
  def find_managed_for_host(hostid:, managed_key:)
85
+ positive_id_attribute(hostid, "hostid")
79
86
  result = @client.api_request(
80
87
  method: "trigger.get",
81
88
  params: {
82
89
  hostids: hostid,
83
90
  tags: [{ tag: "zabbix_manager_id", value: managed_key, operator: 1 }],
84
- output: ["triggerid", "description"],
91
+ output: "extend",
92
+ expandExpression: true,
93
+ selectDependencies: ["triggerid"],
85
94
  selectTags: "extend"
86
95
  }
87
96
  )
97
+ response_objects(result)
98
+ result.each { |trigger| response_identifier(trigger["triggerid"]) }
88
99
  raise Conflict, "multiple triggers use managed key #{managed_key}" if result.length > 1
89
100
 
90
101
  result.first
@@ -96,7 +107,12 @@ class ZabbixManager
96
107
  id = normalized_ids([triggerid]).first
97
108
  params = { triggerids: id, output: "extend" }
98
109
  params[:selectDependencies] = select_dependencies if select_dependencies
99
- result = @client.api_request(method: "trigger.get", params: params)
110
+ result = response_objects(@client.api_request(method: "trigger.get", params: params))
111
+ result.each do |trigger|
112
+ unless response_identifier(trigger["triggerid"]).to_s == id
113
+ raise ProtocolError, "trigger.get returned an unexpected trigger ID"
114
+ end
115
+ end
100
116
  raise Conflict, "multiple triggers use triggerid #{id}" if result.length > 1
101
117
 
102
118
  result.first
@@ -106,7 +122,8 @@ class ZabbixManager
106
122
  # 可通过 Client 的 upsert_lock 接入跨进程锁。
107
123
  # @return [Integer]
108
124
  def upsert_for_host(data)
109
- attributes = data.deep_symbolize_keys
125
+ attributes = data.deep_symbolize_keys.deep_dup
126
+ positive_id_attribute(attributes[:hostid], "hostid")
110
127
  lock_key = "trigger:#{attributes[:hostid]}:#{attributes[:managed_key] || attributes[:description]}"
111
128
  @client.with_upsert_lock(lock_key) { perform_upsert_for_host(attributes) }
112
129
  end
@@ -114,13 +131,14 @@ class ZabbixManager
114
131
  # 批量启用或停用触发器。
115
132
  # @return [Array<Integer>]
116
133
  def set_status(hostid:, triggerids:, enabled:)
134
+ validate_boolean!(enabled, "enabled")
117
135
  ids = normalized_ids(triggerids)
118
136
  verify_host_ownership!(hostid, ids)
119
137
  result = @client.api_request(
120
138
  method: "trigger.update",
121
139
  params: ids.map { |triggerid| { triggerid: triggerid, status: enabled ? 0 : 1 } }
122
140
  )
123
- Array(result.fetch("triggerids")).map(&:to_i)
141
+ response_ids(result, expected: ids)
124
142
  end
125
143
 
126
144
  # 批量删除明确指定的触发器。
@@ -129,13 +147,14 @@ class ZabbixManager
129
147
  ids = normalized_ids(triggerids)
130
148
  verify_host_ownership!(hostid, ids)
131
149
  result = @client.api_request(method: "trigger.delete", params: ids)
132
- Array(result.fetch("triggerids")).map(&:to_i)
150
+ response_ids(result, expected: ids)
133
151
  end
134
152
 
135
153
  # 使用 trigger.update 完整替换一个触发器的依赖集合。
136
154
  # 传入空集合可清除依赖,返回被更新的触发器 ID。
137
155
  # @return [Integer]
138
156
  def replace_dependencies(hostid:, triggerid:, depends_on:, allow_cross_host_dependencies: false)
157
+ validate_boolean!(allow_cross_host_dependencies, "allow_cross_host_dependencies")
139
158
  id = normalized_ids([triggerid]).first
140
159
  dependency_ids = normalized_ids(depends_on, allow_empty: true)
141
160
  dependencies = dependency_ids.map { |value| { triggerid: value } }
@@ -143,13 +162,14 @@ class ZabbixManager
143
162
 
144
163
  verify_host_ownership!(hostid, [id])
145
164
  verify_host_ownership!(hostid, dependency_ids) if dependency_ids.any? && !allow_cross_host_dependencies
146
- update_dependencies(id, dependencies)
165
+ @client.with_upsert_lock("trigger-dependencies:#{id}") { update_dependencies(id, dependencies) }
147
166
  end
148
167
 
149
168
  # 在保留现有依赖的前提下,为触发器追加一个或多个依赖。
150
169
  # 读改写过程按触发器 ID 加锁,跨进程可通过 Client 的 upsert_lock 协调。
151
170
  # @return [Integer]
152
171
  def add_dependencies(hostid:, triggerid:, depends_on:, allow_cross_host_dependencies: false)
172
+ validate_boolean!(allow_cross_host_dependencies, "allow_cross_host_dependencies")
153
173
  id = normalized_ids([triggerid]).first
154
174
  additions = normalized_ids(depends_on)
155
175
  raise Invalid, "trigger cannot depend on itself" if additions.include?(id)
@@ -177,26 +197,18 @@ class ZabbixManager
177
197
  attributes = data.deep_symbolize_keys
178
198
  hostid = attributes.delete(:hostid)
179
199
  managed_key = attributes.delete(:managed_key)&.to_s
180
- legacy_identity_tags = attributes.delete(:legacy_identity_tags)
181
- raise Invalid, "hostid is required" if hostid.blank?
200
+ positive_id_attribute(hostid, "hostid")
182
201
  raise Invalid, "description is required" if attributes[:description].blank?
183
202
  raise Invalid, "expression is required" if attributes[:expression].blank?
184
203
 
185
204
  if managed_key.present?
186
- raise Invalid, "managed_key is too long" if managed_key.length > 200
205
+ raise Invalid, "managed_key is too long" if managed_key.length > MAX_MANAGED_KEY_LENGTH
187
206
 
188
207
  attributes[:tags] = Array(attributes[:tags]).reject do |tag|
189
208
  tag[:tag].to_s == "zabbix_manager_id" || tag["tag"].to_s == "zabbix_manager_id"
190
209
  end
191
210
  attributes[:tags] << { tag: "zabbix_manager_id", value: managed_key }
192
211
  current = find_managed_for_host(hostid: hostid, managed_key: managed_key)
193
- if current.nil? && legacy_identity_tags.present?
194
- current = find_for_host(
195
- hostid: hostid,
196
- description: attributes[:description],
197
- tags: legacy_identity_tags
198
- )
199
- end
200
212
  else
201
213
  ownership_tags = Array(attributes[:tags]).select do |tag|
202
214
  tag[:tag].to_s == "managed_by" || tag["tag"].to_s == "managed_by"
@@ -204,50 +216,57 @@ class ZabbixManager
204
216
  current = find_for_host(hostid: hostid, description: attributes[:description], tags: ownership_tags)
205
217
  end
206
218
  if current
207
- triggerid = current.fetch("triggerid")
219
+ triggerid = current["triggerid"]
220
+ response_identifier(triggerid)
208
221
  attributes[:tags] = merge_tags(current["tags"], attributes[:tags]) if attributes[:tags]
209
- @client.api_request(method: "trigger.update", params: attributes.merge(triggerid: triggerid))
210
- triggerid.to_i
222
+ result = @client.api_request(method: "trigger.update", params: attributes.merge(triggerid: triggerid))
223
+ response_id(result, expected: [triggerid])
211
224
  else
212
225
  create_managed_trigger(hostid, managed_key, attributes)
213
226
  end
214
227
  end
215
228
 
216
- # 创建触发器;若响应丢失或并发冲突则按管理键回读收敛。
229
+ # 创建触发器;响应丢失时按管理键回读并核对期望状态。
217
230
  # @return [Integer]
218
231
  # @api private
219
232
  private def create_managed_trigger(hostid, managed_key, attributes)
220
233
  result = @client.api_request(method: "trigger.create", params: attributes)
221
- result.fetch("triggerids").first.to_i
234
+ response_id(result)
222
235
  rescue TransportError => original_error
223
- current = recover_created_trigger(hostid: hostid, managed_key: managed_key) if managed_key.present?
224
- return current.fetch("triggerid").to_i if current
236
+ if managed_key.present?
237
+ current = recover_created_trigger(hostid: hostid, managed_key: managed_key, attributes: attributes)
238
+ end
239
+ return response_identifier(current["triggerid"]) if current
225
240
 
226
241
  raise ResultUnknown,
227
242
  "trigger create result is unknown; inspect managed key #{managed_key.inspect} before retrying: " \
228
- "#{original_error.message}"
229
- rescue ApiError => original_error
230
- current = recover_created_trigger(hostid: hostid, managed_key: managed_key) if managed_key.present?
231
- return current.fetch("triggerid").to_i if current
232
-
233
- raise original_error
243
+ "#{original_error.message}"
234
244
  end
235
245
 
236
246
  # 对结果不确定的创建执行短暂退避回读,不重放写请求。
237
247
  # @return [Hash, nil]
238
248
  # @api private
239
- private def recover_created_trigger(hostid:, managed_key:)
249
+ private def recover_created_trigger(hostid:, managed_key:, attributes:)
240
250
  delays = @client.options.fetch(:uncertain_write_delays, DEFAULT_UNCERTAIN_WRITE_DELAYS)
241
251
  delays.each do |delay|
242
252
  sleep(delay) if delay.positive?
243
253
  current = find_managed_for_host(hostid: hostid, managed_key: managed_key)
244
- return current if current
254
+ if current && recovered_trigger_matches?(current, attributes)
255
+ response_identifier(current["triggerid"])
256
+ return current
257
+ end
245
258
  rescue ApiError, TransportError
246
259
  next
247
260
  end
248
261
  nil
249
262
  end
250
263
 
264
+ private def recovered_trigger_matches?(current, attributes)
265
+ desired_tags = Array(attributes[:tags]).map(&:deep_symbolize_keys)
266
+ actual_tags = Array(current["tags"]).map(&:deep_symbolize_keys)
267
+ attributes_match?(current, attributes.except(:tags)) && (desired_tags - actual_tags).empty?
268
+ end
269
+
251
270
  # 合并受管标签,同时保留调用方未覆盖的运维标签。
252
271
  # @return [Array<Hash>]
253
272
  # @api private
@@ -260,24 +279,14 @@ class ZabbixManager
260
279
  retained + desired
261
280
  end
262
281
 
263
- # 统一触发器 ID 并拒绝空集合。
264
- # @return [Array<String>]
265
- # @api private
266
- private def normalized_ids(values, allow_empty: false)
267
- ids = Array(values).filter_map { |value| value.to_s.strip.presence }.uniq
268
- raise Invalid, "triggerids are required" if ids.empty? && !allow_empty
269
-
270
- ids
271
- end
272
-
273
282
  # 回查触发器归属,阻止共享高权限令牌跨主机修改。
274
283
  private def verify_host_ownership!(hostid, ids)
275
- raise Invalid, "hostid is required" if hostid.blank?
284
+ positive_id_attribute(hostid, "hostid")
276
285
 
277
286
  result = @client.api_request(
278
287
  method: "trigger.get", params: { hostids: hostid, triggerids: ids, output: ["triggerid"] }
279
288
  )
280
- owned_ids = result.map { |trigger| trigger.fetch("triggerid").to_s }
289
+ owned_ids = response_objects(result).map { |trigger| response_identifier(trigger["triggerid"]).to_s }
281
290
  foreign_ids = ids - owned_ids
282
291
  raise Conflict, "triggers do not belong to host #{hostid}: #{foreign_ids.join(", ")}" if foreign_ids.any?
283
292
  end
@@ -287,7 +296,7 @@ class ZabbixManager
287
296
  result = @client.api_request(
288
297
  method: "trigger.update", params: { triggerid: triggerid, dependencies: dependencies }
289
298
  )
290
- result.fetch("triggerids").first.to_i
299
+ response_id(result, expected: [triggerid])
291
300
  end
292
301
  end
293
302
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZabbixManager
4
+ # 用户组成员和主机组权限替换;空数组表示清空,省略字段不参与更新。
5
+ class UserGroups < Resource
6
+ # @return [String] 原生用户组 API 前缀
7
+ def method_name
8
+ "usergroup"
9
+ end
10
+
11
+ # @return [String] 用户组 ID 字段
12
+ def key
13
+ "usrgrpid"
14
+ end
15
+
16
+ # 完整替换主机组权限;空数组明确清空,Zabbix 6.2 起保留独立的模板组权限。
17
+ # @param user_group_id [Integer, String] 目标用户组 ID
18
+ # @param host_group_ids [Array<Integer, String>] 完整期望主机组集合
19
+ # @param permission [Integer, String] 0 拒绝、2 只读、3 读写
20
+ # @return [Integer] 回执确认的用户组 ID
21
+ def replace_host_group_permissions(user_group_id:, host_group_ids:, permission: 2)
22
+ group_id = normalized_ids([user_group_id]).first
23
+ host_ids = collection_ids(host_group_ids, "host_group_ids", allow_empty: true)
24
+ access = integer_attribute(permission, "permission")
25
+ raise Invalid, "permission must be 0, 2, or 3" unless [0, 2, 3].include?(access)
26
+
27
+ rights_field = api_version >= Gem::Version.new("6.2") ? :hostgroup_rights : :rights
28
+ result = @client.api_request(
29
+ method: "usergroup.update",
30
+ params: { usrgrpid: group_id, rights_field => host_ids.map { |id| { id: id, permission: access } } }
31
+ )
32
+ response_id(result, expected: [group_id])
33
+ end
34
+
35
+ # 完整替换各用户组成员;空 user_ids 明确移除现有成员。
36
+ # @param user_group_ids [Array<Integer, String>] 待更新用户组 ID
37
+ # @param user_ids [Array<Integer, String>] 每组的完整期望用户集合
38
+ # @return [Array<Integer>] 回执确认的全部用户组 ID
39
+ def replace_users(user_group_ids:, user_ids:)
40
+ group_ids = collection_ids(user_group_ids, "user_group_ids")
41
+ users = collection_ids(user_ids, "user_ids", allow_empty: true)
42
+ membership = api_version >= Gem::Version.new("6.0") ?
43
+ { users: users.map { |id| { userid: id } } } : { userids: users }
44
+ result = @client.api_request(
45
+ method: "usergroup.update",
46
+ params: group_ids.map { |id| membership.merge(usrgrpid: id) }
47
+ )
48
+ response_ids(result, expected: group_ids)
49
+ end
50
+
51
+ private
52
+
53
+ def api_version
54
+ Gem::Version.new(@client.api_version)
55
+ end
56
+
57
+ def collection_ids(values, name, allow_empty: false)
58
+ raise Invalid, "#{name} must be an Array" unless values.is_a?(Array)
59
+
60
+ normalized_ids(values, allow_empty: allow_empty)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZabbixManager
4
+ # 主机宏由 hostid + macro 唯一定位,全局宏由 macro 唯一定位。
5
+ class UserMacros < Resource
6
+ # @return [String] 宏业务标识
7
+ def identify
8
+ "macro"
9
+ end
10
+
11
+ # @return [String] API 模块名
12
+ def method_name
13
+ "usermacro"
14
+ end
15
+
16
+ # @return [String] 主机宏 ID 字段
17
+ def key
18
+ "hostmacroid"
19
+ end
20
+
21
+ # @return [Hash] 主机宏复合身份
22
+ def identity_filter(data)
23
+ attributes = data.deep_symbolize_keys
24
+ { macro: attributes.fetch(:macro), hostid: attributes.fetch(:hostid) }
25
+ end
26
+
27
+ # @return [Integer, nil] 唯一主机宏 ID
28
+ def get_id(data)
29
+ attributes = identity_filter(data)
30
+ unique_macro_id(get_full_data(attributes), "hostmacroid")
31
+ end
32
+
33
+ # @return [Integer, nil] 唯一全局宏 ID
34
+ def get_id_global(data)
35
+ attributes = data.deep_symbolize_keys
36
+ unique_macro_id(get_full_data_global(macro: attributes.fetch(:macro)), "globalmacroid")
37
+ end
38
+
39
+ # @return [Array<Hash>] 主机宏查询结果
40
+ def get_full_data(data)
41
+ attributes = data.deep_symbolize_keys
42
+ params = { filter: attributes.except(:hostid) }
43
+ params[:hostids] = attributes[:hostid] if attributes.key?(:hostid)
44
+ get_raw(params)
45
+ end
46
+
47
+ # @return [Array<Hash>] 全局宏查询结果
48
+ def get_full_data_global(data)
49
+ get_raw(globalmacro: true, filter: data.deep_symbolize_keys)
50
+ end
51
+
52
+ # @return [Integer, nil] 新建主机宏 ID
53
+ def create(data)
54
+ write_macro("create", data, "hostmacroids")
55
+ end
56
+
57
+ # @return [Integer, nil] 新建全局宏 ID
58
+ def create_global(data)
59
+ write_macro("createglobal", data, "globalmacroids")
60
+ end
61
+
62
+ # @return [Integer, nil] 删除主机宏 ID
63
+ def delete(ids)
64
+ requested = normalized_ids(ids)
65
+ write_macro("delete", requested, "hostmacroids", expected: requested)
66
+ end
67
+
68
+ # @return [Integer, nil] 删除全局宏 ID
69
+ def delete_global(ids)
70
+ requested = normalized_ids(ids)
71
+ write_macro("deleteglobal", requested, "globalmacroids", expected: requested)
72
+ end
73
+
74
+ # @return [Integer, nil] 更新主机宏 ID
75
+ def update(data)
76
+ write_macro("update", data, "hostmacroids", expected: [data.with_indifferent_access[:hostmacroid]])
77
+ end
78
+
79
+ # @return [Integer, nil] 更新全局宏 ID
80
+ def update_global(data)
81
+ write_macro("updateglobal", data, "globalmacroids", expected: [data.with_indifferent_access[:globalmacroid]])
82
+ end
83
+
84
+ # @return [Integer, nil] 已存在或新建全局宏 ID
85
+ def get_or_create_global(data)
86
+ get_id_global(data) || create_global(data)
87
+ end
88
+
89
+ # @return [Integer, nil] 新建或更新全局宏 ID
90
+ def create_or_update_global(data)
91
+ attributes = data.deep_symbolize_keys
92
+ id = get_id_global(attributes)
93
+ id ? update_global(attributes.merge(globalmacroid: id)) : create_global(attributes)
94
+ end
95
+
96
+ private
97
+
98
+ def unique_macro_id(result, id_field)
99
+ response_objects(result)
100
+ raise Conflict, "multiple macros match the requested identity" if result.length > 1
101
+
102
+ response_identifier(result.first[id_field], id_field) if result.first
103
+ end
104
+
105
+ def write_macro(operation, params, id_field, expected: nil)
106
+ normalized_ids(expected) if expected
107
+ result = @client.api_request(method: "usermacro.#{operation}", params: params)
108
+ expected ? response_ids(result, id_field, expected: expected).first : response_id(result, id_field)
109
+ end
110
+ end
111
+ end
@@ -1,71 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ZabbixManager
4
- class Users < Basic
5
- # 返回 Zabbix API 中用户对象的方法名前缀。
6
- #
7
- # @return [String] 用户对象的方法名前缀
4
+ # 用户资源与媒介配置替换;字段名称按服务器版本转换。
5
+ class Users < Resource
6
+ # @return [String] API 模块名
8
7
  def method_name
9
8
  "user"
10
9
  end
11
10
 
12
- # 返回用户对象批量结果中的 ID 字段名。
13
- #
14
- # @return [String] 用户 ID 复数字段名
15
- def keys
16
- "userids"
17
- end
18
-
19
- # 返回用户对象的主键字段名。
20
- #
21
- # @return [String] 用户对象的主键字段名
22
- def key
23
- "userid"
24
- end
25
-
26
- # 返回用于唯一识别用户的业务字段名。
27
- #
28
- # @return [String] 用户对象的业务标识字段名
11
+ # @return [String] 用户名字段
29
12
  def identify
30
- "alias"
13
+ Gem::Version.new(@client.api_version) >= Gem::Version.new("5.4") ? "username" : "alias"
31
14
  end
32
15
 
33
- # 按指定动作批量更新用户媒介配置。
34
- #
16
+ # 替换指定用户的完整媒介配置。
35
17
  # @param data [Hash] 包含 userids 和 media
36
- # @param action [String] user API 的动作名
37
- # @return [Integer, nil] 首个已更新用户的 ID
38
- def medias_helper(data, action)
18
+ # @return [Integer, nil] 首个更新用户的 ID
19
+ def update_medias(data)
20
+ attributes = data.deep_symbolize_keys
21
+ ids = normalized_ids(attributes.fetch(:userids))
22
+ media_field = Gem::Version.new(@client.api_version) >= Gem::Version.new("5.4") ? :medias : :user_medias
39
23
  result = @client.api_request(
40
- method: "user.#{action}",
41
- params: data[:userids].map do |t|
42
- {
43
- userid: t,
44
- user_medias: data[:media]
45
- }
24
+ method: "user.update",
25
+ params: attributes.fetch(:userids).map do |userid|
26
+ { userid: userid, media_field => attributes.fetch(:media) }
46
27
  end
47
28
  )
48
- result ? result["userids"][0].to_i : nil
49
- end
50
-
51
- # 为指定用户添加媒介配置,当前通过 user.update 完成。
52
- #
53
- # @param data [Hash] 包含 userids 和 media
54
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
55
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
56
- # @return [Integer, nil] 首个已更新用户的 ID
57
- def add_medias(data)
58
- medias_helper(data, "update")
59
- end
60
-
61
- # 批量替换指定用户的媒介配置。
62
- #
63
- # @param data [Hash] 包含 userids 和 media
64
- # @raise [ApiError] Zabbix API 返回业务错误时抛出
65
- # @raise [TransportError] Zabbix 服务返回非成功 HTTP 状态时抛出
66
- # @return [Integer, nil] 首个已更新用户的 ID
67
- def update_medias(data)
68
- medias_helper(data, "update")
29
+ response_ids(result, expected: ids).first
69
30
  end
70
31
  end
71
32
  end