zabbix_manager 5.1.4 → 5.1.6

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -9
  3. data/LICENSE +1 -1
  4. data/README.md +202 -8
  5. data/lib/zabbix_manager/basic/basic_alias.rb +25 -3
  6. data/lib/zabbix_manager/basic/basic_func.rb +32 -61
  7. data/lib/zabbix_manager/basic/basic_init.rb +28 -13
  8. data/lib/zabbix_manager/basic/basic_logic.rb +172 -128
  9. data/lib/zabbix_manager/classes/actions.rb +21 -11
  10. data/lib/zabbix_manager/classes/applications.rb +12 -17
  11. data/lib/zabbix_manager/classes/configurations.rb +20 -5
  12. data/lib/zabbix_manager/classes/drules.rb +11 -23
  13. data/lib/zabbix_manager/classes/errors.rb +16 -28
  14. data/lib/zabbix_manager/classes/events.rb +6 -2
  15. data/lib/zabbix_manager/classes/graphs.rb +41 -34
  16. data/lib/zabbix_manager/classes/hostgroups.rb +53 -3
  17. data/lib/zabbix_manager/classes/hostinterfaces.rb +172 -12
  18. data/lib/zabbix_manager/classes/hosts.rb +197 -68
  19. data/lib/zabbix_manager/classes/httptests.rb +18 -20
  20. data/lib/zabbix_manager/classes/items.rb +199 -72
  21. data/lib/zabbix_manager/classes/maintenance.rb +6 -2
  22. data/lib/zabbix_manager/classes/mediatypes.rb +7 -63
  23. data/lib/zabbix_manager/classes/problems.rb +75 -57
  24. data/lib/zabbix_manager/classes/proxies.rb +48 -7
  25. data/lib/zabbix_manager/classes/roles.rb +47 -36
  26. data/lib/zabbix_manager/classes/screens.rb +45 -14
  27. data/lib/zabbix_manager/classes/scripts.rb +17 -7
  28. data/lib/zabbix_manager/classes/server.rb +7 -1
  29. data/lib/zabbix_manager/classes/templates.rb +59 -21
  30. data/lib/zabbix_manager/classes/triggers.rb +253 -66
  31. data/lib/zabbix_manager/classes/usergroups.rb +28 -6
  32. data/lib/zabbix_manager/classes/usermacros.rb +119 -33
  33. data/lib/zabbix_manager/classes/users.rb +39 -7
  34. data/lib/zabbix_manager/classes/valuemaps.rb +13 -19
  35. data/lib/zabbix_manager/client.rb +311 -135
  36. data/lib/zabbix_manager/http_transport.rb +192 -0
  37. data/lib/zabbix_manager/log_sanitizer.rb +68 -0
  38. data/lib/zabbix_manager/monitoring.rb +691 -0
  39. data/lib/zabbix_manager/version.rb +1 -1
  40. data/lib/zabbix_manager.rb +118 -40
  41. data/zabbix_manager.gemspec +21 -8
  42. metadata +208 -22
  43. data/lib/zabbix_manager/classes/unusable.rb +0 -11
@@ -2,103 +2,232 @@
2
2
 
3
3
  class ZabbixManager
4
4
  class Hosts < Basic
5
- # 与 Zabbix API 交互的主机方法名称
5
+ # 返回主机对应的 Zabbix API 模块名。
6
6
  def method_name
7
7
  "host"
8
8
  end
9
9
 
10
- # 用于通过 Zabbix API 标识特定主机对象的 id 字段名称
10
+ # 使用技术主机名作为通用 CRUD 标识。
11
11
  def identify
12
12
  "host"
13
13
  end
14
14
 
15
- # 在通过 Zabbix API 创建主机对象时使用的默认选项
15
+ # 返回创建主机时的克制默认值。
16
16
  def default_options
17
17
  {
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,
18
+ status: 0,
35
19
  inventory_mode: 1
36
20
  }
37
21
  end
38
22
 
39
- # 通过 Zabbix API 从指定的 key 值获取主机对象数据
23
+ # 按主机 ID 查询完整主机及接口、群组和模板。
40
24
  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
- }
25
+ log "[DEBUG] Call dump_by_id with parameters: #{data.inspect}"
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}"
56
-
57
- result = get_raw({
58
- filter: { name: [data].flatten },
59
- output: [key]
60
- }
38
+ # 从指定主机批量解除模板关联。
39
+ def unlink_templates(data)
40
+ result = @client.api_request(
41
+ method: "host.massRemove",
42
+ params: {
43
+ hostids: data[:hosts_id],
44
+ templateids: data[:templates_id]
45
+ }
61
46
  )
62
- result.empty? ? nil : result.map { |i| { key.to_sym => i[key] } }
47
+ !result.empty?
48
+ end
49
+
50
+ # 使用显式群组和接口创建主机,不注入隐藏凭据。
51
+ def create(data)
52
+ attributes = default_options.merge(data.deep_symbolize_keys)
53
+ validate_create_attributes!(attributes)
54
+ result = @client.api_request(method: "host.create", params: attributes)
55
+ first_id(result, "hostids")
56
+ end
57
+
58
+ # 规范化并校验设备定义,不执行远端查询或写入。
59
+ # 已存在设备允许只更新部分字段,新设备的群组和接口由 create 再次严格校验。
60
+ # @return [Hash]
61
+ def validate(data)
62
+ attributes = data.deep_symbolize_keys
63
+ raise Invalid, "host is required" if attributes[:host].blank?
64
+
65
+ hostinterfaces.validate(attributes[:interfaces]) if attributes.key?(:interfaces)
66
+ attributes
63
67
  end
64
68
 
65
- # 使用 Zabbix API 创建或更新 Zabbix 对象
66
- def create_or_update(data)
67
- log "[DEBUG] 调用 #{method_name}.create_or_update 方法,参数: #{data.inspect}"
69
+ # 幂等同步主机元数据和显式接口,调用方保持业务字段权威。
70
+ def reconcile(data)
71
+ attributes = validate(data)
72
+ host = attributes.fetch(:host)
73
+
74
+ hostid = get_id(host: host)
75
+ unless hostid
76
+ outcome, hostid = @client.with_upsert_lock("host-create:#{host}") do
77
+ current_id = get_id(host: host)
78
+ current_id ? [:existing, current_id] : [:created, create(attributes)]
79
+ end
80
+ return hostid.to_i if outcome == :created
81
+ end
68
82
 
69
- hostid = get_id(identify.to_sym => data[identify.to_sym])
70
- return create(data) unless hostid
83
+ update_existing(hostid, attributes)
84
+ end
71
85
 
72
- interface_id = get_interfaceid(hostid)
73
- raise ZbxError, "#{data.inspect} get_interfaceid 查无记录" unless interface_id
86
+ # 更新主机元数据,并把接口同步委托给带主机锁的接口模块。
87
+ # @return [Integer]
88
+ # @api private
89
+ private def update_existing(hostid, attributes)
90
+ attributes = attributes.dup
74
91
 
75
- dump_data = data.dup
76
- interfaces = dump_data.delete(:interfaces)&.merge(interfaceid: interface_id)
77
- params = dump_data.merge(interfaces: interfaces).merge(key.to_sym => hostid.to_s)
78
- update(params)
92
+ interfaces = Array.wrap(attributes.delete(:interfaces))
93
+ hostinterfaces.validate(interfaces)
94
+ update_attributes = attributes.merge(hostid: hostid)
95
+ @client.api_request(method: "host.update", params: update_attributes)
96
+ hostinterfaces.reconcile_for_host(hostid: hostid, interfaces: interfaces) if interfaces.any?
97
+ hostid.to_i
79
98
  end
80
99
 
81
- # 从 API 中基于提供的 hostid 获取 Zabbix interface 的 id
82
- def get_interfaceid(hostid)
83
- log "[DEBUG] 调用 #{method_name}.get_interfaceid 方法,参数: #{hostid.inspect}"
100
+ # 查询指定主机的全部接口。
101
+ def interfaces_for(hostid)
102
+ hostinterfaces.for_host(hostid)
103
+ end
84
104
 
85
- result = @client.api_request(
86
- method: "hostinterface.get",
87
- params: { output: "interfaceid", hostids: hostid }
88
- )
89
- result&.[](0)&.[]("interfaceid")&.to_i
105
+ # 返回主机的第一个接口 ID;高频业务应优先使用 hostinterfaces。
106
+ def get_interface_id(hostid)
107
+ interfaces_for(hostid).first&.fetch("interfaceid", nil)
90
108
  end
91
109
 
92
- # 在 Hosts 中使用 Zabbix API 取消关联/移除模板
93
- def unlink_templates(data)
94
- result = @client.api_request(
95
- method: "host.massRemove",
96
- params: {
97
- hostids: data[:hosts_id],
98
- templates: data[:templates_id]
99
- }
100
- )
101
- result.empty? ? false : true
110
+ # 按技术主机名查询主机 ID。
111
+ def get_host_id(name)
112
+ find_host_id(host: name)
102
113
  end
114
+
115
+ # 按可见名称查询主机 ID。
116
+ def get_hostid_by_name(name)
117
+ find_host_id(name: name)
118
+ end
119
+
120
+ # 用技术名、可见名或接口 IP/DNS 解析唯一主机。
121
+ def find_by_candidates(candidates)
122
+ matches = Array(candidates).filter_map { |candidate| candidate.to_s.strip.presence }.uniq.flat_map do |candidate|
123
+ hosts = [find_host_by_filter(host: candidate), find_host_by_filter(name: candidate)].compact
124
+ interface = find_interface_by_endpoint(candidate)
125
+ hosts << find_host_by_filter(hostid: interface["hostid"]) if interface
126
+ hosts
127
+ end
128
+ matches.uniq! { |host| host.fetch("hostid") }
129
+ if matches.length > 1
130
+ raise Conflict, "host candidates resolve to multiple hosts; provide an explicit host object"
131
+ end
132
+
133
+ matches.first
134
+ end
135
+
136
+ # 按主机 ID 查询唯一主机,并返回表达式需要的技术名称。
137
+ # @return [Hash, nil]
138
+ def find_by_id(hostid)
139
+ find_host_by_filter(hostid: hostid)
140
+ end
141
+
142
+ # 批量启用或停用主机。
143
+ # @return [Array<Integer>]
144
+ def set_status(hostids, enabled:)
145
+ ids = Array(hostids).filter_map { |value| value.to_s.strip.presence }.uniq
146
+ raise Invalid, "hostids are required" if ids.empty?
147
+
148
+ params = ids.map { |hostid| { hostid: hostid, status: enabled ? 0 : 1 } }
149
+ result = @client.api_request(method: "host.update", params: params)
150
+ Array(result.fetch("hostids")).map(&:to_i)
151
+ end
152
+
153
+ # 把可见名称对应的主机更新为稳定技术标识。
154
+ def update_host_to_serial(data)
155
+ attributes = data.deep_symbolize_keys
156
+ hostid = get_hostid_by_name(attributes[:name])
157
+ return nil unless hostid
158
+
159
+ attributes.delete(:templates)
160
+ @client.api_request(method: "host.update", params: attributes.merge(hostid: hostid))
161
+ hostid.to_i
162
+ end
163
+
164
+ private
165
+
166
+ # 校验创建主机所需的技术名、群组和接口。
167
+ def validate_create_attributes!(attributes)
168
+ raise Invalid, "host is required" if attributes[:host].blank?
169
+ raise Invalid, "groups are required when creating a host" if Array.wrap(attributes[:groups]).empty?
170
+ raise Invalid, "interfaces are required when creating a host" if Array.wrap(attributes[:interfaces]).empty?
171
+
172
+ attributes[:interfaces] = hostinterfaces.validate_for_create(attributes[:interfaces])
173
+ end
174
+
175
+ # 使用精确过滤查询唯一主机。
176
+ def find_host_by_filter(filter)
177
+ result = @client.api_request(
178
+ method: "host.get",
179
+ params: {
180
+ output: %w[hostid host name status],
181
+ filter: filter,
182
+ selectInterfaces: %w[interfaceid ip dns]
183
+ }
184
+ )
185
+ raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
186
+
187
+ result.first
188
+ end
189
+
190
+ # 按 IP 和 DNS 端点查询唯一主机接口。
191
+ def find_interface_by_endpoint(candidate)
192
+ matches = []
193
+ %i[ip dns].each do |field|
194
+ result = @client.api_request(
195
+ method: "hostinterface.get",
196
+ params: { filter: { field => candidate }, output: ["hostid"] }
197
+ )
198
+ matches.concat(result)
199
+ end
200
+ matches.uniq! { |interface| interface.fetch("hostid") }
201
+ raise Conflict, "interface endpoint #{candidate} belongs to multiple hosts" if matches.length > 1
202
+
203
+ matches.first
204
+ end
205
+
206
+ # 只查询唯一主机 ID。
207
+ def find_host_id(filter)
208
+ result = @client.api_request(
209
+ method: "host.get",
210
+ params: {
211
+ output: ["hostid"],
212
+ filter: filter
213
+ }
214
+ )
215
+ raise Conflict, "host lookup is ambiguous for #{filter.inspect}" if result.length > 1
216
+
217
+ result.first&.fetch("hostid", nil)
218
+ end
219
+
220
+ # 从 Zabbix 创建响应中提取首个整数 ID。
221
+ def first_id(result, key_name)
222
+ value = result.fetch(key_name).first
223
+ value && value.to_i
224
+ end
225
+
226
+ # 延迟构建接口模块并复用当前客户端。
227
+ # @return [HostInterfaces]
228
+ # @api private
229
+ def hostinterfaces
230
+ @hostinterfaces ||= HostInterfaces.new(@client)
231
+ end
103
232
  end
104
233
  end
@@ -2,39 +2,37 @@
2
2
 
3
3
  class ZabbixManager
4
4
  class HttpTests < Basic
5
- # 用于通过 Zabbix API 与 HttpTests 交互的方法名称
5
+ # 返回 Web 场景对象对应的 Zabbix API 方法前缀。
6
+ #
7
+ # @return [String]
6
8
  def method_name
7
9
  "httptest"
8
10
  end
9
11
 
10
- # 用于通过 Zabbix API 标识特定 HttpTest 对象的 id 字段名称
12
+ # 返回 Web 场景用于业务识别的字段名。
13
+ #
14
+ # @return [String]
11
15
  def identify
12
16
  "name"
13
17
  end
14
18
 
15
- # 通过 Zabbix API 创建 HttpTest 对象时使用的默认选项
19
+ # 返回创建 Web 场景时使用的默认步骤列表。
20
+ #
21
+ # @return [Hash] Web 场景默认属性
16
22
  def default_options
17
23
  {
18
- hostid: nil,
19
- name: nil,
20
- steps: []
24
+ steps: []
21
25
  }
22
26
  end
23
27
 
24
- # 通过 Zabbix API 获取或创建 HttpTest 对象
25
- def get_or_create(data)
26
- log "[DEBUG] Call get_or_create with parameters: #{data.inspect}"
27
-
28
- unless (id = get_id(name: data[:name], hostid: data[:hostid]))
29
- id = create(data)
30
- end
31
- id
32
- end
33
-
34
- # 通过 Zabbix API 创建或更新 HttpTest 对象
35
- def create_or_update(data)
36
- httptestid = get_id(name: data[:name], hostid: data[:hostid])
37
- httptestid ? update(data.merge(httptestid: httptestid)) : create(data)
28
+ # 生成由 Web 场景名称和所属主机构成的稳定查询条件。
29
+ #
30
+ # @param data [Hash] 包含 name 和 hostid 的 Web 场景属性
31
+ # @raise [KeyError] 缺少 name 或 hostid 时抛出
32
+ # @return [Hash] Web 场景唯一查询条件
33
+ def identity_filter(data)
34
+ attributes = data.deep_symbolize_keys
35
+ { name: attributes.fetch(:name), hostid: attributes.fetch(:hostid) }
38
36
  end
39
37
  end
40
38
  end
@@ -2,97 +2,224 @@
2
2
 
3
3
  class ZabbixManager
4
4
  class Items < Basic
5
- # 用于通过 Zabbix API 与 Items 交互的方法名称
5
+ DEFAULT_OPTIONS = {
6
+ delay: "1m",
7
+ history: "1h",
8
+ status: 0,
9
+ value_type: 3
10
+ }.freeze
11
+ ITEM_TYPES = [0, 2, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22].freeze
12
+ REQUIRED_INTERFACE_TYPES = [0, 12, 16, 17, 20].freeze
13
+ REQUIRED_PARAMS_TYPES = [11, 13, 14, 15, 21, 22].freeze
14
+
15
+ # 返回监控项对应的 Zabbix API 模块名。
16
+ #
17
+ # @return [String]
6
18
  def method_name
7
19
  "item"
8
20
  end
9
21
 
10
- # 用于通过 Zabbix API 标识特定 Item 对象的 id 字段名称
22
+ # 使用名称作为通用 CRUD 的业务标识。
23
+ #
24
+ # @return [String]
11
25
  def identify
12
26
  "name"
13
27
  end
14
28
 
15
- # 通过 Zabbix API 创建 Item 对象时使用的默认选项
29
+ # 返回创建监控项时的克制默认值。
30
+ #
31
+ # @return [Hash]
16
32
  def default_options
17
- {
18
- name: nil,
19
- key_: nil,
20
- hostid: nil,
21
- delay: 60,
22
- history: 3600,
23
- status: 0,
24
- type: 7,
25
- snmp_community: "",
26
- snmp_oid: "",
27
- value_type: 3,
28
- data_type: 0,
29
- trapper_hosts: "localhost",
30
- snmp_port: 161,
31
- units: "",
32
- multiplier: 0,
33
- delta: 0,
34
- snmpv3_securityname: "",
35
- snmpv3_securitylevel: 0,
36
- snmpv3_authpassphrase: "",
37
- snmpv3_privpassphrase: "",
38
- formula: 0,
39
- trends: 86_400,
40
- logtimefmt: "",
41
- valuemapid: 0,
42
- delay_flex: "",
43
- authtype: 0,
44
- username: "",
45
- password: "",
46
- publickey: "",
47
- privatekey: "",
48
- params: "",
49
- ipmi_sensor: ""
50
- }
33
+ DEFAULT_OPTIONS.dup
34
+ end
35
+
36
+ # 使用主机和名称组成通用 CRUD 的查询边界。
37
+ #
38
+ # @param data [Hash] Needs to include name and hostid to properly identify Items via Zabbix API
39
+ # @raise [ApiError] Error returned when there is a problem with the Zabbix API call.
40
+ # @raise [TransportError] Error raised when HTTP status from Zabbix Server response is not a 200 OK.
41
+ # @return [Integer] Zabbix object id
42
+ def identity_filter(data)
43
+ attributes = data.deep_symbolize_keys
44
+ { name: attributes.fetch(:name), hostid: attributes.fetch(:hostid) }
45
+ end
46
+
47
+ # 查询主机监控项,可按稳定 key 集合过滤并选择关联对象。
48
+ # @return [Array<Hash>]
49
+ def for_host(hostid, keys: nil, output: "extend", select_preprocessing: nil, select_tags: nil)
50
+ raise Invalid, "hostid is required" if hostid.blank?
51
+
52
+ params = { hostids: hostid, output: output }
53
+ params[:filter] = { key_: keys } if keys.present?
54
+ params[:selectPreprocessing] = select_preprocessing if select_preprocessing
55
+ params[:selectTags] = select_tags if select_tags
56
+ @client.api_request(method: "item.get", params: params)
57
+ end
58
+
59
+ # 按主机和稳定 key 查询唯一监控项。
60
+ # @return [Hash, nil]
61
+ def find_by_key(hostid:, key:)
62
+ result = for_host(hostid, keys: key, output: ["itemid", "key_", "name"])
63
+ raise Conflict, "multiple items use key #{key} on host #{hostid}" if result.length > 1
64
+
65
+ result.first
51
66
  end
52
67
 
53
- # 通过 Zabbix API 获取或创建 Item 对象
54
- def get_or_create(data)
55
- log "[DEBUG] Call #{method_name}.get_or_create with parameters: #{data.inspect}"
68
+ # 按 hostid + key_ 幂等创建或更新监控项。
69
+ # @return [Integer]
70
+ def upsert_by_key(data)
71
+ attributes = validate_item!(data)
56
72
 
57
- if (id = get_id(name: data[:name], hostid: data[:hostid]))
58
- id
73
+ current = find_by_key(hostid: attributes[:hostid], key: attributes[:key_])
74
+ if current
75
+ itemid = current.fetch("itemid")
76
+ @client.api_request(
77
+ method: "item.update",
78
+ params: attributes.except(:hostid).merge(itemid: itemid)
79
+ )
80
+ itemid.to_i
59
81
  else
60
- create(data)
82
+ create_attributes = default_options.merge(attributes)
83
+ validate_create_item!(create_attributes)
84
+ result = @client.api_request(method: "item.create", params: create_attributes)
85
+ result.fetch("itemids").first.to_i
61
86
  end
62
87
  end
63
88
 
64
- # 通过 Zabbix API 创建或更新 Item 对象
65
- def create_or_update(data)
66
- itemid = get_id(name: data[:name], hostid: data[:hostid])
67
- itemid ? update(data.merge(itemid: itemid)) : create(data)
89
+ # 先校验整批数据,再按稳定 key 逐项幂等写入。
90
+ # @return [Array<Integer>]
91
+ def upsert_many(collection)
92
+ items = Array(collection).map { |data| validate_item!(data) }
93
+ identities = items.map { |item| [item[:hostid].to_s, item[:key_].to_s] }
94
+ duplicate = identities.tally.find { |_identity, count| count > 1 }&.first
95
+ raise Invalid, "duplicate hostid + key_ identity #{duplicate.join(":")}" if duplicate
96
+
97
+ items.map { |item| upsert_by_key(item) }
98
+ end
99
+
100
+ # 批量启用或停用监控项。
101
+ # @return [Array<Integer>]
102
+ def set_status(hostid:, itemids:, enabled:)
103
+ ids = normalized_ids(itemids, "itemids")
104
+ verify_host_ownership!(hostid, ids)
105
+ result = @client.api_request(
106
+ method: "item.update",
107
+ params: ids.map { |itemid| { itemid: itemid, status: enabled ? 0 : 1 } }
108
+ )
109
+ Array(result.fetch("itemids")).map(&:to_i)
110
+ end
111
+
112
+ # 批量删除明确指定的监控项。
113
+ # @return [Array<Integer>]
114
+ def delete_many(hostid:, itemids:)
115
+ ids = normalized_ids(itemids, "itemids")
116
+ verify_host_ownership!(hostid, ids)
117
+ result = @client.api_request(method: "item.delete", params: ids)
118
+ Array(result.fetch("itemids")).map(&:to_i)
68
119
  end
69
120
 
70
- # 通过 Zabbix API 获取特定主机和接口下的监控项
71
- def get_interface_items(hostid, name)
72
- # 记录调试信息,标识方法调用以及传递的参数
73
- log "[DEBUG] 调用 #{method_name}.get_interface_items,参数:hostid #{hostid.inspect},name #{name.inspect}"
74
-
75
- # 自动剔除收尾空白字符,规范化接口名
76
- _name = name&.gsub(%r{[^/0-9]}, "")&.strip
77
- iface = "#{_name}("
78
-
79
- # 使用 Zabbix API 进行模糊查询,获取特定主机和接口下的监控项
80
- result = get_raw(
81
- {
82
- output: "extend",
83
- hostids: hostid,
84
- search: {
85
- name: iface
86
- }
121
+ # 返回主机中可能表示接口流量的已启用监控项。
122
+ # 方向和接口精确匹配由 Monitoring 业务层负责。
123
+ # @return [Array<Hash>]
124
+ def monitored_traffic_candidates(hostid)
125
+ @client.api_request(
126
+ method: "item.get",
127
+ params: {
128
+ hostids: [hostid],
129
+ monitored: true,
130
+ output: %w[itemid hostid name key_ snmp_oid value_type status units],
131
+ selectPreprocessing: "extend",
132
+ sortfield: "name",
133
+ sortorder: "ASC"
87
134
  }
88
- ).select {
89
- |item| item["snmp_oid"].match?(/(1.3.6.1.2.1.31.1.1.1.(6|10|15)|1.3.6.1.2.1.2.2.1.8)./)
90
- }.sort_by {
91
- |item| item["key_"]
92
- }
93
-
94
- # 如果结果为空,则返回 nil;否则,返回排序后的结果
95
- result.empty? ? nil : result
135
+ )
136
+ end
137
+
138
+ # 按主机接口幂等创建或更新 DNS 解析监控项,并返回监控项 ID。
139
+ # DNS 名称会进入 item key,因此拒绝可能改变 key 参数结构的分隔符。
140
+ # @return [Integer]
141
+ def upsert_dns_item(hostid:, interfaceid:, dns_name:)
142
+ name = dns_name.to_s.strip
143
+ raise Invalid, "dns_name is required" if name.blank?
144
+ raise Invalid, "dns_name contains unsupported item key delimiters" if name.match?(/[\[\],]/)
145
+
146
+ itemid = upsert_by_key(
147
+ hostid: hostid,
148
+ interfaceid: interfaceid,
149
+ name: "【DNS域名解析监控】#{name}",
150
+ key_: "net.dns.record[,#{name},A,2,2]",
151
+ type: 0,
152
+ value_type: 1,
153
+ delay: "1m",
154
+ history: "90d",
155
+ timeout: "3s"
156
+ )
157
+ log "Ensured DNS monitoring item for #{name}"
158
+ itemid
159
+ rescue StandardError => e
160
+ log "Failed to ensure DNS monitoring item: #{e.class}"
161
+ raise
96
162
  end
163
+
164
+ private
165
+
166
+ # 规范化并校验幂等写入所需字段。
167
+ # @return [Hash]
168
+ # @api private
169
+ def validate_item!(data)
170
+ attributes = data.deep_symbolize_keys
171
+ raise Invalid, "hostid is required" if attributes[:hostid].blank?
172
+ raise Invalid, "key_ is required" if attributes[:key_].blank?
173
+ raise Invalid, "name is required" if attributes[:name].blank?
174
+
175
+ attributes
176
+ end
177
+
178
+ # 校验 item.create 的通用必填字段和 SNMP 条件字段。
179
+ def validate_create_item!(attributes)
180
+ raise Invalid, "type is required when creating an item" if attributes[:type].blank?
181
+ raise Invalid, "value_type is required when creating an item" if attributes[:value_type].blank?
182
+
183
+ type = Integer(attributes[:type])
184
+ raise Invalid, "unsupported item type #{type}" unless ITEM_TYPES.include?(type)
185
+
186
+ if REQUIRED_INTERFACE_TYPES.include?(type) && attributes[:interfaceid].blank?
187
+ raise Invalid, "interfaceid is required for item type #{type}"
188
+ end
189
+ if REQUIRED_PARAMS_TYPES.include?(type) && attributes[:params].blank?
190
+ raise Invalid, "params is required for item type #{type}"
191
+ end
192
+
193
+ raise Invalid,
194
+ "master_itemid is required for a dependent item" if type == 18 && attributes[:master_itemid].blank?
195
+ raise Invalid, "url is required for an HTTP item" if type == 19 && attributes[:url].blank?
196
+ return unless type == 20
197
+
198
+ raise Invalid, "snmp_oid is required for an SNMP item" if attributes[:snmp_oid].blank?
199
+ rescue ArgumentError, TypeError
200
+ raise Invalid, "type must be a supported integer"
201
+ end
202
+
203
+ # 回查监控项归属,阻止共享高权限令牌跨主机修改。
204
+ def verify_host_ownership!(hostid, ids)
205
+ raise Invalid, "hostid is required" if hostid.blank?
206
+
207
+ result = @client.api_request(
208
+ method: "item.get", params: { hostids: hostid, itemids: ids, output: ["itemid"] }
209
+ )
210
+ owned_ids = result.map { |item| item.fetch("itemid").to_s }
211
+ foreign_ids = ids - owned_ids
212
+ raise Conflict, "items do not belong to host #{hostid}: #{foreign_ids.join(", ")}" if foreign_ids.any?
213
+ end
214
+
215
+ # 统一批量 ID 并拒绝空集合。
216
+ # @return [Array<String>]
217
+ # @api private
218
+ def normalized_ids(values, name)
219
+ ids = Array(values).filter_map { |value| value.to_s.strip.presence }.uniq
220
+ raise Invalid, "#{name} are required" if ids.empty?
221
+
222
+ ids
223
+ end
97
224
  end
98
225
  end
@@ -2,12 +2,16 @@
2
2
 
3
3
  class ZabbixManager
4
4
  class Maintenance < Basic
5
- # 用于通过 Zabbix API 与维护窗口对象交互的方法名称
5
+ # 返回维护期对象对应的 Zabbix API 方法前缀。
6
+ #
7
+ # @return [String]
6
8
  def method_name
7
9
  "maintenance"
8
10
  end
9
11
 
10
- # 用于通过 Zabbix API 识别特定维护窗口对象的 id 字段名称
12
+ # 返回维护期对象用于业务识别的字段名。
13
+ #
14
+ # @return [String]
11
15
  def identify
12
16
  "name"
13
17
  end