ezKit 1.11.16__py3-none-any.whl → 1.11.18__py3-none-any.whl
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.
- ezKit/dockerhub.py +30 -3
- ezKit/zabbix.py +912 -0
- {ezKit-1.11.16.dist-info → ezKit-1.11.18.dist-info}/METADATA +1 -1
- {ezKit-1.11.16.dist-info → ezKit-1.11.18.dist-info}/RECORD +7 -6
- {ezKit-1.11.16.dist-info → ezKit-1.11.18.dist-info}/LICENSE +0 -0
- {ezKit-1.11.16.dist-info → ezKit-1.11.18.dist-info}/WHEEL +0 -0
- {ezKit-1.11.16.dist-info → ezKit-1.11.18.dist-info}/top_level.txt +0 -0
ezKit/dockerhub.py
CHANGED
@@ -9,7 +9,7 @@ def get_latest_tags(url: str, limit: int = 20, proxies: dict | None = None) -> l
|
|
9
9
|
|
10
10
|
try:
|
11
11
|
|
12
|
-
logger.info(f"{info} [
|
12
|
+
logger.info(f"{info} [开始]")
|
13
13
|
|
14
14
|
# url = "https://hub.docker.com/v2/repositories/library/postgres/tags"
|
15
15
|
|
@@ -39,10 +39,37 @@ def get_latest_tags(url: str, limit: int = 20, proxies: dict | None = None) -> l
|
|
39
39
|
# 提取 tags
|
40
40
|
tags = [tag["name"] for tag in results]
|
41
41
|
|
42
|
-
logger.success(f"{info} [
|
42
|
+
logger.success(f"{info} [成功]")
|
43
43
|
return tags
|
44
44
|
|
45
45
|
except Exception as e:
|
46
|
-
logger.error(f"{info} [
|
46
|
+
logger.error(f"{info} [错误]")
|
47
47
|
logger.exception(e)
|
48
48
|
return None
|
49
|
+
|
50
|
+
# def get_all_tags():
|
51
|
+
# url = "https://hub.docker.com/v2/repositories/library/postgres/tags"
|
52
|
+
# tags = []
|
53
|
+
# page = 1
|
54
|
+
|
55
|
+
# while True:
|
56
|
+
# response = requests.get(url, params={"page": page, "page_size": 100}, timeout=10)
|
57
|
+
# if response.status_code != 200:
|
58
|
+
# print(f"请求失败,状态码: {response.status_code}")
|
59
|
+
# break
|
60
|
+
|
61
|
+
# data = response.json()
|
62
|
+
# results = data.get("results", [])
|
63
|
+
|
64
|
+
# if not results:
|
65
|
+
# break
|
66
|
+
|
67
|
+
# tags.extend(tag["name"] for tag in results)
|
68
|
+
# page += 1
|
69
|
+
|
70
|
+
# return tags
|
71
|
+
|
72
|
+
|
73
|
+
# 获取所有 Postgres tags 并输出
|
74
|
+
# postgres_tags = get_all_tags()
|
75
|
+
# print("\n".join(postgres_tags))
|
ezKit/zabbix.py
ADDED
@@ -0,0 +1,912 @@
|
|
1
|
+
import sys
|
2
|
+
import time
|
3
|
+
from copy import deepcopy
|
4
|
+
|
5
|
+
import requests
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
from . import utils
|
9
|
+
|
10
|
+
|
11
|
+
class Zabbix():
|
12
|
+
"""Zabbix"""
|
13
|
+
|
14
|
+
api: str | None = None
|
15
|
+
auth: str | None = None
|
16
|
+
|
17
|
+
# ----------------------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
def __init__(self, api: str, username: str, password: str):
|
20
|
+
"""Initiation"""
|
21
|
+
self.api = api
|
22
|
+
self.auth = self.login(username=username, password=password)
|
23
|
+
|
24
|
+
# ----------------------------------------------------------------------------------------------
|
25
|
+
|
26
|
+
def request(
|
27
|
+
self,
|
28
|
+
method: str,
|
29
|
+
params: dict | list,
|
30
|
+
debug: bool = False,
|
31
|
+
**kwargs
|
32
|
+
) -> dict | None:
|
33
|
+
"""Request Zabbix API"""
|
34
|
+
|
35
|
+
try:
|
36
|
+
|
37
|
+
# https://www.zabbix.com/documentation/current/en/manual/api#performing-requests
|
38
|
+
# The request must have the Content-Type header set to one of these values:
|
39
|
+
# application/json-rpc, application/json or application/jsonrequest.
|
40
|
+
headers = {"Content-Type": "application/json-rpc"}
|
41
|
+
|
42
|
+
# https://www.zabbix.com/documentation/6.0/en/manual/api#authentication
|
43
|
+
# jsonrpc - the version of the JSON-RPC protocol used by the API; the Zabbix API implements JSON-RPC version 2.0
|
44
|
+
# method - the API method being called
|
45
|
+
# params - parameters that will be passed to the API method
|
46
|
+
# id - an arbitrary identifier of the request (请求标识符, 这里使用UNIX时间戳作为唯一标示)
|
47
|
+
# auth - a user authentication token; since we don't have one yet, it's set to null
|
48
|
+
data: dict = {
|
49
|
+
"jsonrpc": "2.0",
|
50
|
+
"method": method,
|
51
|
+
"params": params,
|
52
|
+
"auth": None if method == "apiinfo.version" else self.auth,
|
53
|
+
"id": int(time.time())
|
54
|
+
}
|
55
|
+
|
56
|
+
if utils.isTrue(debug, bool):
|
57
|
+
logger.info(f"request data: {data}")
|
58
|
+
|
59
|
+
if self.api is None:
|
60
|
+
logger.error("api is None")
|
61
|
+
return None
|
62
|
+
|
63
|
+
# 请求API
|
64
|
+
response = requests.post(self.api, headers=headers, json=data, timeout=10, **kwargs)
|
65
|
+
|
66
|
+
if utils.isTrue(debug, bool):
|
67
|
+
logger.info(f"response: {response}")
|
68
|
+
|
69
|
+
if response.status_code != 200:
|
70
|
+
logger.error(f"response status code: {response.status_code}")
|
71
|
+
return None
|
72
|
+
|
73
|
+
# 返回结果
|
74
|
+
return response.json()
|
75
|
+
|
76
|
+
except Exception as e:
|
77
|
+
logger.exception(e)
|
78
|
+
return None
|
79
|
+
|
80
|
+
# ----------------------------------------------------------------------------------------------
|
81
|
+
|
82
|
+
def login(self, username: str, password: str) -> str | None:
|
83
|
+
"""User Login"""
|
84
|
+
|
85
|
+
info: str = "Login"
|
86
|
+
|
87
|
+
try:
|
88
|
+
|
89
|
+
logger.info(f"{info} [started]")
|
90
|
+
|
91
|
+
data: dict | None = self.request(
|
92
|
+
method="user.login",
|
93
|
+
params={"username": username, "password": password}
|
94
|
+
)
|
95
|
+
|
96
|
+
if data is None:
|
97
|
+
return None
|
98
|
+
|
99
|
+
if data.get("result") is None:
|
100
|
+
logger.error(f"{info} [response result is None]")
|
101
|
+
return None
|
102
|
+
|
103
|
+
logger.success(f"{info} [succeeded]")
|
104
|
+
return data["result"]
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
logger.error(f"{info} [failed]")
|
108
|
+
logger.exception(e)
|
109
|
+
return None
|
110
|
+
|
111
|
+
# ----------------------------------------------------------------------------------------------
|
112
|
+
|
113
|
+
def logout(self) -> bool:
|
114
|
+
"""User Logout"""
|
115
|
+
|
116
|
+
info: str = "Logout"
|
117
|
+
|
118
|
+
try:
|
119
|
+
|
120
|
+
logger.info(f"{info} [started]")
|
121
|
+
|
122
|
+
data = self.request(method="user.logout", params={})
|
123
|
+
|
124
|
+
if data is not None and data.get("result"):
|
125
|
+
logger.success(f'{info} [succeeded]')
|
126
|
+
return True
|
127
|
+
|
128
|
+
if data is not None and data.get("error"):
|
129
|
+
logger.error(f"{info} [error: {data.get('error',{}).get('data')}]")
|
130
|
+
return False
|
131
|
+
|
132
|
+
logger.error(f"{info} [failed]")
|
133
|
+
return False
|
134
|
+
|
135
|
+
except Exception as e:
|
136
|
+
logger.error(f"{info} [failed]")
|
137
|
+
logger.exception(e)
|
138
|
+
return False
|
139
|
+
|
140
|
+
# ----------------------------------------------------------------------------------------------
|
141
|
+
|
142
|
+
# def logout_and_exit(self):
|
143
|
+
# """Logout and Exit"""
|
144
|
+
|
145
|
+
# try:
|
146
|
+
# self.logout()
|
147
|
+
# except Exception as e:
|
148
|
+
# logger.exception(e)
|
149
|
+
# finally:
|
150
|
+
# sys.exit()
|
151
|
+
|
152
|
+
# ----------------------------------------------------------------------------------------------
|
153
|
+
|
154
|
+
def get_version(self) -> str | None:
|
155
|
+
"""Get version"""
|
156
|
+
|
157
|
+
info: str = "Get version"
|
158
|
+
|
159
|
+
try:
|
160
|
+
|
161
|
+
logger.info(f"{info} [started]")
|
162
|
+
|
163
|
+
data = self.request('apiinfo.version', [])
|
164
|
+
|
165
|
+
if data is None:
|
166
|
+
return None
|
167
|
+
|
168
|
+
logger.success(f"{info} [succeeded]")
|
169
|
+
return data['result']
|
170
|
+
|
171
|
+
except Exception as e:
|
172
|
+
logger.error(f"{info} [failed]")
|
173
|
+
logger.exception(e)
|
174
|
+
return None
|
175
|
+
|
176
|
+
# ----------------------------------------------------------------------------------------------
|
177
|
+
|
178
|
+
def get_ids_by_template_name(self, name: str) -> list | None:
|
179
|
+
"""Get ids by template name"""
|
180
|
+
|
181
|
+
# https://www.zabbix.com/documentation/7.0/en/manual/api/reference/template
|
182
|
+
# name: string/array
|
183
|
+
# example: 'Linux by Zabbix agent' / ['Linux by Zabbix agent', 'Linux by Zabbix agent active']
|
184
|
+
# 如果 name 为 '' (空), 返回所有 template id
|
185
|
+
|
186
|
+
info: str = "Get ids by template name"
|
187
|
+
|
188
|
+
try:
|
189
|
+
|
190
|
+
logger.info(f"{info} [started]")
|
191
|
+
|
192
|
+
data = self.request('template.get', {'output': 'templateid', 'filter': {'name': name}})
|
193
|
+
|
194
|
+
if data is None:
|
195
|
+
return None
|
196
|
+
|
197
|
+
if not utils.isTrue(data['result'], list):
|
198
|
+
logger.error(f"{info} [error: {data['error']}]")
|
199
|
+
return None
|
200
|
+
|
201
|
+
logger.success(f"{info} [succeeded]")
|
202
|
+
return [i['templateid'] for i in data['result']]
|
203
|
+
|
204
|
+
except Exception as e:
|
205
|
+
logger.error(f"{info} [failed]")
|
206
|
+
logger.exception(e)
|
207
|
+
return None
|
208
|
+
|
209
|
+
# ----------------------------------------------------------------------------------------------
|
210
|
+
|
211
|
+
def get_ids_by_hostgroup_name(self, name: str) -> list | None:
|
212
|
+
"""Get ids by hostgroup name"""
|
213
|
+
|
214
|
+
# # https://www.zabbix.com/documentation/7.0/en/manual/api/reference/hostgroup
|
215
|
+
# name: string/array
|
216
|
+
# example: 'Linux servers' / ['Linux servers', 'Discovered hosts']
|
217
|
+
# 如果 name 为 '' (空), 返回所有 hostgroup id
|
218
|
+
|
219
|
+
info: str = "Get ids by hostgroup name"
|
220
|
+
|
221
|
+
try:
|
222
|
+
|
223
|
+
logger.info(f"{info} [started]")
|
224
|
+
|
225
|
+
# Zabbix 6.0 -> output: groupid
|
226
|
+
data = self.request('hostgroup.get', {'output': 'extend', 'filter': {'name': name}})
|
227
|
+
|
228
|
+
if data is None:
|
229
|
+
return None
|
230
|
+
|
231
|
+
if not utils.isTrue(data.get('result'), list):
|
232
|
+
logger.error(f"{info} [error: {data['error']}]")
|
233
|
+
return None
|
234
|
+
|
235
|
+
logger.success(f"{info} [succeeded]")
|
236
|
+
return [i['groupid'] for i in data['result']]
|
237
|
+
|
238
|
+
except Exception as e:
|
239
|
+
logger.error(f"{info} [failed]")
|
240
|
+
logger.exception(e)
|
241
|
+
return None
|
242
|
+
|
243
|
+
# ----------------------------------------------------------------------------------------------
|
244
|
+
|
245
|
+
def get_hosts_by_template_name(self, name: str, output: str = 'extend', **kwargs) -> list | None:
|
246
|
+
"""Get hosts by template name"""
|
247
|
+
|
248
|
+
# name: string/array
|
249
|
+
# example: 'Linux by Zabbix agent' / ['Linux by Zabbix agent', 'Linux by Zabbix agent active']
|
250
|
+
# 如果 name 为 '' (空), 返回所有 host
|
251
|
+
|
252
|
+
info: str = "Get hosts by template name"
|
253
|
+
|
254
|
+
try:
|
255
|
+
|
256
|
+
logger.info(f"{info} [started]")
|
257
|
+
|
258
|
+
templates = self.request('template.get', {'output': ['templateid'], 'filter': {'host': name}})
|
259
|
+
|
260
|
+
if templates is None:
|
261
|
+
return None
|
262
|
+
|
263
|
+
if not utils.isTrue(templates.get('result'), list):
|
264
|
+
logger.error(f"{info} [error: {templates['error']}]")
|
265
|
+
return None
|
266
|
+
|
267
|
+
templateids = [i['templateid'] for i in templates['result']]
|
268
|
+
|
269
|
+
hosts = self.request('host.get', {'output': output, 'templateids': templateids, **kwargs})
|
270
|
+
|
271
|
+
if hosts is None:
|
272
|
+
return None
|
273
|
+
|
274
|
+
if not utils.isTrue(hosts.get('result'), list):
|
275
|
+
logger.error(f"{info} [error: {hosts['error']}]")
|
276
|
+
return None
|
277
|
+
|
278
|
+
logger.success(f"{info} [succeeded]")
|
279
|
+
return hosts['result']
|
280
|
+
|
281
|
+
except Exception as e:
|
282
|
+
logger.error(f"{info} [failed]")
|
283
|
+
logger.exception(e)
|
284
|
+
return None
|
285
|
+
|
286
|
+
# ----------------------------------------------------------------------------------------------
|
287
|
+
|
288
|
+
def get_hosts_by_hostgroup_name(self, name: str, output: str | list = 'extend', **kwargs) -> list | None:
|
289
|
+
"""Get hosts by hostgroup name"""
|
290
|
+
|
291
|
+
# name: string/array
|
292
|
+
# example: 'Linux servers' / ['Linux servers', 'Discovered hosts']
|
293
|
+
# 如果 name 为 '' (空), 返回所有 hosts
|
294
|
+
|
295
|
+
info: str = "Get hosts by hostgroup name"
|
296
|
+
|
297
|
+
try:
|
298
|
+
|
299
|
+
logger.info(f"{info} [started]")
|
300
|
+
|
301
|
+
ids = self.get_ids_by_hostgroup_name(name)
|
302
|
+
|
303
|
+
if ids is None:
|
304
|
+
return None
|
305
|
+
|
306
|
+
hosts = self.request('host.get', {'output': output, 'groupids': ids, **kwargs})
|
307
|
+
|
308
|
+
if hosts is None:
|
309
|
+
return None
|
310
|
+
|
311
|
+
if not utils.isTrue(hosts.get('result'), list):
|
312
|
+
logger.error(f"{info} [error: {hosts['error']}]")
|
313
|
+
return None
|
314
|
+
|
315
|
+
logger.success(f"{info} [succeeded]")
|
316
|
+
return hosts['result']
|
317
|
+
|
318
|
+
except Exception as e:
|
319
|
+
logger.error(f"{info} [failed]")
|
320
|
+
logger.exception(e)
|
321
|
+
return None
|
322
|
+
|
323
|
+
# ----------------------------------------------------------------------------------------------
|
324
|
+
|
325
|
+
def get_interface_by_host_id(self, hostid: str, output: str = 'extend') -> list | None:
|
326
|
+
"""Get interface by host id"""
|
327
|
+
|
328
|
+
# hostids: string/array
|
329
|
+
# example: '10792' / ['10792', '10793']
|
330
|
+
|
331
|
+
info: str = "Get interface by host id"
|
332
|
+
|
333
|
+
try:
|
334
|
+
|
335
|
+
logger.info(f"{info} [started]")
|
336
|
+
|
337
|
+
data = self.request('hostinterface.get', {'output': output, 'hostids': hostid})
|
338
|
+
|
339
|
+
if data is None:
|
340
|
+
return None
|
341
|
+
|
342
|
+
if not utils.isTrue(data.get('result'), list):
|
343
|
+
logger.error(f"{info} [error: {data['error']}]")
|
344
|
+
return None
|
345
|
+
|
346
|
+
logger.success(f"{info} [succeeded]")
|
347
|
+
return data['result']
|
348
|
+
|
349
|
+
except Exception as e:
|
350
|
+
logger.error(f"{info} [failed]")
|
351
|
+
logger.exception(e)
|
352
|
+
return None
|
353
|
+
|
354
|
+
# ----------------------------------------------------------------------------------------------
|
355
|
+
|
356
|
+
def available_hosts(self, hosts: list) -> tuple | None:
|
357
|
+
"""可用服务器"""
|
358
|
+
|
359
|
+
try:
|
360
|
+
|
361
|
+
if not utils.isTrue(hosts, list):
|
362
|
+
logger.error("hosts is not a list")
|
363
|
+
return None
|
364
|
+
|
365
|
+
# 可用服务器, 不可用服务器
|
366
|
+
available, unavailable = [], []
|
367
|
+
|
368
|
+
# 服务器排查
|
369
|
+
for host in hosts:
|
370
|
+
if host['interfaces'][0]['available'] != '1':
|
371
|
+
unavailable.append(host['name'])
|
372
|
+
else:
|
373
|
+
available.append(host)
|
374
|
+
|
375
|
+
return available, unavailable
|
376
|
+
|
377
|
+
except Exception as e:
|
378
|
+
logger.exception(e)
|
379
|
+
return None
|
380
|
+
|
381
|
+
# ----------------------------------------------------------------------------------------------
|
382
|
+
|
383
|
+
def get_history_by_item_key(
|
384
|
+
self,
|
385
|
+
hosts: list,
|
386
|
+
time_from: int,
|
387
|
+
time_till: int,
|
388
|
+
item_key: str,
|
389
|
+
data_type: int = 3
|
390
|
+
) -> list | None:
|
391
|
+
"""get history by item key"""
|
392
|
+
|
393
|
+
# 1. 根据 item key 获取 item id, 通过 item id 获取 history
|
394
|
+
# 2. 根据 host 的 item id 和 history 的 item id 将数据提取为一个 history list
|
395
|
+
# 3. 根据 history list 中的 clock 排序, 然后将 history list 整合到 host 中
|
396
|
+
# 4. 返回包含有 item key, item id 和 history list 的 host 的 host list
|
397
|
+
#
|
398
|
+
# 通过 Item Key 获取 Item history
|
399
|
+
#
|
400
|
+
# hosts: 主机列表
|
401
|
+
# time_from: 开始时间
|
402
|
+
# time_till: 结束时间
|
403
|
+
# item_key: Item Key
|
404
|
+
# data_type: 数据类型
|
405
|
+
#
|
406
|
+
# 参考文档:
|
407
|
+
#
|
408
|
+
# https://www.zabbix.com/documentation/6.0/en/manual/api/reference/history/get
|
409
|
+
#
|
410
|
+
# history
|
411
|
+
#
|
412
|
+
# 0 - numeric float
|
413
|
+
# 1 - character
|
414
|
+
# 2 - log
|
415
|
+
# 3 - numeric unsigned
|
416
|
+
# 4 - text
|
417
|
+
#
|
418
|
+
# Default: 3
|
419
|
+
#
|
420
|
+
# 默认数据类型是 numeric unsigned (整数), 如果 history.get 返回的数据为 None, 有可能是 data_type 类型不对
|
421
|
+
|
422
|
+
info: str = "Get history by item key"
|
423
|
+
|
424
|
+
try:
|
425
|
+
|
426
|
+
logger.info(f"{info} [started]")
|
427
|
+
|
428
|
+
# match True:
|
429
|
+
# case True if not utils.isTrue(hosts, list):
|
430
|
+
# logger.error(f"{info} [hosts is not a list]")
|
431
|
+
# return None
|
432
|
+
# case True if not utils.isTrue(time_from, int):
|
433
|
+
# logger.error(f"{info} [time_from is not a integer]")
|
434
|
+
# return None
|
435
|
+
# case True if not utils.isTrue(time_till, int):
|
436
|
+
# logger.error(f"{info} [time_till is not a integer]")
|
437
|
+
# return None
|
438
|
+
# case True if not utils.isTrue(item_key, str):
|
439
|
+
# logger.error(f"{info} [item_key is not a string]")
|
440
|
+
# return None
|
441
|
+
|
442
|
+
# 初始化变量
|
443
|
+
# item_ids 获取历史数据时使用
|
444
|
+
# item_history 历史数据集合, 最后返回
|
445
|
+
item_ids: list = []
|
446
|
+
item_history: list = []
|
447
|
+
|
448
|
+
# Deep Copy(拷贝数据)
|
449
|
+
# 父函数的变量是 list 或者 dict 类型, 父函数将变量传递个子函数, 如果子函数对变量数据进行了修改, 那么父函数的变量的数据也会被修改
|
450
|
+
# 为了避免出现这种问题, 可以使用 Deep Copy 拷贝一份数据, 避免子函数修改父函数的变量的数据
|
451
|
+
hosts = deepcopy(hosts)
|
452
|
+
|
453
|
+
# --------------------------------------------------------------------------------------
|
454
|
+
|
455
|
+
# Get Item
|
456
|
+
hostids = [i['hostid'] for i in hosts]
|
457
|
+
item_params = {
|
458
|
+
'output': ['name', 'itemid', 'hostid'],
|
459
|
+
'hostids': hostids,
|
460
|
+
'filter': {'key_': item_key}
|
461
|
+
}
|
462
|
+
items = self.request('item.get', item_params)
|
463
|
+
|
464
|
+
if items is None:
|
465
|
+
return None
|
466
|
+
|
467
|
+
# --------------------------------------------------------------------------------------
|
468
|
+
|
469
|
+
# 因为 history 获取的顺序是乱的, 为了使输出和 hosts 列表顺序一致, 将 Item ID 追加到 hosts, 然后遍历 hosts 列表输出
|
470
|
+
if not utils.isTrue(items.get('result'), list):
|
471
|
+
logger.error(f"{info} [item key {item_key} not find]")
|
472
|
+
return None
|
473
|
+
|
474
|
+
for host in hosts:
|
475
|
+
if not isinstance(items, dict):
|
476
|
+
return
|
477
|
+
|
478
|
+
item: dict = next((item_object for item_object in items['result'] if host['hostid'] == item_object['hostid']), '') # type: ignore
|
479
|
+
|
480
|
+
if utils.isTrue(item, dict) and item.get('itemid') is not None:
|
481
|
+
host['itemkey'] = item_key
|
482
|
+
host['itemid'] = item['itemid']
|
483
|
+
item_ids.append(item['itemid'])
|
484
|
+
item_history.append(host)
|
485
|
+
|
486
|
+
# 如果 ID 列表为空, 则返回 None
|
487
|
+
if not utils.isTrue(item_ids, list):
|
488
|
+
logger.error(f"{info} [item key {item_key} not find]")
|
489
|
+
return None
|
490
|
+
|
491
|
+
# --------------------------------------------------------------------------------------
|
492
|
+
|
493
|
+
# Get History
|
494
|
+
history_params = {
|
495
|
+
'output': 'extend',
|
496
|
+
'history': data_type,
|
497
|
+
'itemids': item_ids,
|
498
|
+
'time_from': time_from,
|
499
|
+
'time_till': time_till
|
500
|
+
}
|
501
|
+
history = self.request('history.get', history_params)
|
502
|
+
|
503
|
+
if history is None:
|
504
|
+
return None
|
505
|
+
|
506
|
+
# --------------------------------------------------------------------------------------------------
|
507
|
+
|
508
|
+
if not utils.isTrue(history.get('result'), list):
|
509
|
+
logger.error(f"{info} [item history not find]")
|
510
|
+
return None
|
511
|
+
|
512
|
+
for item in item_history:
|
513
|
+
# 根据 itemid 提取数据
|
514
|
+
item_history_data = [history_result for history_result in history['result'] if item['itemid'] == history_result['itemid']]
|
515
|
+
# 根据 clock 排序
|
516
|
+
item_history_data = utils.list_dict_sorted_by_key(item_history_data, 'clock')
|
517
|
+
# 整合数据
|
518
|
+
item['history'] = item_history_data
|
519
|
+
|
520
|
+
logger.success(f"{info} [succeeded]")
|
521
|
+
return item_history
|
522
|
+
|
523
|
+
except Exception as e:
|
524
|
+
logger.error(f"{info} [failed]")
|
525
|
+
logger.exception(e)
|
526
|
+
return None
|
527
|
+
|
528
|
+
# ----------------------------------------------------------------------------------------------
|
529
|
+
|
530
|
+
def get_history_by_interface(
|
531
|
+
self,
|
532
|
+
hosts: list,
|
533
|
+
interfaces: list,
|
534
|
+
time_from: int,
|
535
|
+
time_till: int,
|
536
|
+
direction: str
|
537
|
+
) -> list | None:
|
538
|
+
"""获取网卡历史数据"""
|
539
|
+
|
540
|
+
info: str = "Get history by interface"
|
541
|
+
|
542
|
+
try:
|
543
|
+
|
544
|
+
logger.info(f"{info} [started]")
|
545
|
+
|
546
|
+
# match True:
|
547
|
+
# case True if not utils.isTrue(hosts, list):
|
548
|
+
# logger.error('ERROR!! hosts is not list or none')
|
549
|
+
# return None
|
550
|
+
# case True if not utils.isTrue(interfaces, list):
|
551
|
+
# logger.error('ERROR!! interfaces is not list or none')
|
552
|
+
# return None
|
553
|
+
# case True if not utils.isTrue(time_from, int):
|
554
|
+
# logger.error('ERROR!! time_from is not integer or zero')
|
555
|
+
# return None
|
556
|
+
# case True if not utils.isTrue(time_till, int):
|
557
|
+
# logger.error('ERROR!! time_till is not integer or zero')
|
558
|
+
# return None
|
559
|
+
# case True if not utils.isTrue(direction, str):
|
560
|
+
# logger.error('ERROR!! direction is not string or none')
|
561
|
+
# return None
|
562
|
+
|
563
|
+
# 创建一个只有 网卡名称 的 列表
|
564
|
+
interfaces_names: set = set(interface['interface'] for interface in interfaces)
|
565
|
+
|
566
|
+
# 创建一个 Key 为 网卡名称 的 dictionary
|
567
|
+
interfaces_dict: dict = {key: [] for key in interfaces_names}
|
568
|
+
|
569
|
+
# 汇集 相同网卡名称 的 IP
|
570
|
+
for interface in interfaces:
|
571
|
+
interfaces_dict[interface['interface']].append(interface['host'])
|
572
|
+
|
573
|
+
# 获取历史数据
|
574
|
+
history: list = []
|
575
|
+
|
576
|
+
for key, value in interfaces_dict.items():
|
577
|
+
|
578
|
+
hosts_by_ip = [host for v in value for host in hosts if v == host['interfaces'][0]['ip']]
|
579
|
+
|
580
|
+
data = self.get_history_by_item_key(
|
581
|
+
hosts=hosts_by_ip,
|
582
|
+
time_from=time_from,
|
583
|
+
time_till=time_till,
|
584
|
+
item_key=f'net.if.{direction}["{key}"]',
|
585
|
+
data_type=3
|
586
|
+
)
|
587
|
+
|
588
|
+
if data is None:
|
589
|
+
continue
|
590
|
+
|
591
|
+
history += data
|
592
|
+
|
593
|
+
logger.success(f"{info} [succeeded]")
|
594
|
+
|
595
|
+
# 根据 name 排序
|
596
|
+
return utils.list_dict_sorted_by_key(history, 'name')
|
597
|
+
|
598
|
+
except Exception as e:
|
599
|
+
logger.error(f"{info} [failed]")
|
600
|
+
logger.exception(e)
|
601
|
+
return None
|
602
|
+
|
603
|
+
# ----------------------------------------------------------------------------------------------
|
604
|
+
|
605
|
+
def get_ips_by_hostgroup_name(self, hostgroup_name: str) -> list | None:
|
606
|
+
"""Get ips by hostgroup name"""
|
607
|
+
|
608
|
+
info: str = "Get ips by hostgroup name"
|
609
|
+
|
610
|
+
try:
|
611
|
+
|
612
|
+
logger.info(f"{info} [started]")
|
613
|
+
|
614
|
+
hosts = self.get_hosts_by_hostgroup_name(hostgroup_name)
|
615
|
+
|
616
|
+
if hosts is None:
|
617
|
+
return None
|
618
|
+
|
619
|
+
hostids = [i["hostid"] for i in hosts]
|
620
|
+
|
621
|
+
hostinterface = self.request(method="hostinterface.get", params={"hostids": hostids})
|
622
|
+
|
623
|
+
if hostinterface is None:
|
624
|
+
return None
|
625
|
+
|
626
|
+
logger.success(f"{info} [succeeded]")
|
627
|
+
return [i["ip"] for i in hostinterface.get("result", [])]
|
628
|
+
|
629
|
+
except Exception as e:
|
630
|
+
logger.error(f"{info} [failed]")
|
631
|
+
logger.exception(e)
|
632
|
+
return None
|
633
|
+
|
634
|
+
# ----------------------------------------------------------------------------------------------
|
635
|
+
|
636
|
+
def create_object(
|
637
|
+
self,
|
638
|
+
ips: list,
|
639
|
+
item: dict | None = None,
|
640
|
+
trigger: dict | None = None,
|
641
|
+
graph: bool | dict = False
|
642
|
+
) -> bool:
|
643
|
+
"""create object"""
|
644
|
+
|
645
|
+
# 创建对象
|
646
|
+
#
|
647
|
+
# ips: IP列表
|
648
|
+
#
|
649
|
+
# item:
|
650
|
+
#
|
651
|
+
# name
|
652
|
+
# key_
|
653
|
+
#
|
654
|
+
# trigger:
|
655
|
+
#
|
656
|
+
# description
|
657
|
+
# expression (必须包含 {host}, 用于定义HOST)
|
658
|
+
#
|
659
|
+
# 参考文档:
|
660
|
+
#
|
661
|
+
# https://www.zabbix.com/documentation/6.0/en/manual/api/reference/item/object
|
662
|
+
# https://www.zabbix.com/documentation/6.0/en/manual/config/items/itemtypes/zabbix_agent
|
663
|
+
# https://www.zabbix.com/documentation/6.0/en/manual/api/reference/trigger/object
|
664
|
+
#
|
665
|
+
# type:
|
666
|
+
#
|
667
|
+
# 0 - Zabbix agent
|
668
|
+
# 2 - Zabbix trapper
|
669
|
+
# 3 - Simple check
|
670
|
+
# 5 - Zabbix internal
|
671
|
+
# 7 - Zabbix agent (active)
|
672
|
+
# 9 - Web item
|
673
|
+
# 10 - External check
|
674
|
+
# 11 - Database monitor
|
675
|
+
# 12 - IPMI agent
|
676
|
+
# 13 - SSH agent
|
677
|
+
# 14 - Telnet agent
|
678
|
+
# 15 - Calculated
|
679
|
+
# 16 - JMX agent
|
680
|
+
# 17 - SNMP trap
|
681
|
+
# 18 - Dependent item
|
682
|
+
# 19 - HTTP agent
|
683
|
+
# 20 - SNMP agent
|
684
|
+
# 21 - Script
|
685
|
+
#
|
686
|
+
# value_type:
|
687
|
+
#
|
688
|
+
# 0 - numeric float
|
689
|
+
# 1 - character
|
690
|
+
# 2 - log
|
691
|
+
# 3 - numeric unsigned
|
692
|
+
# 4 - text
|
693
|
+
#
|
694
|
+
# priority (integer): Severity of the trigger
|
695
|
+
#
|
696
|
+
# 0 - (default) not classified
|
697
|
+
# 1 - information
|
698
|
+
# 2 - warning
|
699
|
+
# 3 - average
|
700
|
+
# 4 - high
|
701
|
+
# 5 - disaster
|
702
|
+
|
703
|
+
info: str = "Create object"
|
704
|
+
|
705
|
+
try:
|
706
|
+
|
707
|
+
logger.info(f"{info} [started]")
|
708
|
+
|
709
|
+
if not utils.isTrue(ips, list):
|
710
|
+
logger.error(f"{info} [ips is not a list]")
|
711
|
+
return False
|
712
|
+
|
713
|
+
for ip in ips:
|
714
|
+
|
715
|
+
item_id: str | None = None
|
716
|
+
|
717
|
+
# ----------------------------------------------------------------------------------
|
718
|
+
|
719
|
+
# Host Object
|
720
|
+
|
721
|
+
logger.info(f"{info} [get host object]")
|
722
|
+
|
723
|
+
response = self.request('hostinterface.get', {'filter': {'ip': ip}, 'selectHosts': ['host']})
|
724
|
+
|
725
|
+
if response is None:
|
726
|
+
continue
|
727
|
+
|
728
|
+
# match True:
|
729
|
+
# case True if utils.isTrue(response, dict) and utils.isTrue(response.get('result'), list):
|
730
|
+
# logger.success(f"{info} [get host object] success: {response['result'][0]['hosts'][0]['host']}")
|
731
|
+
# case True if utils.isTrue(response, dict) and response.get('error'):
|
732
|
+
# logger.error(f"{info} [get host object] error: {response.get('error', {}).get('data')}")
|
733
|
+
# continue
|
734
|
+
# case _:
|
735
|
+
# logger.error(f"{info} [get host object] error: {ip}")
|
736
|
+
# continue
|
737
|
+
|
738
|
+
host = response['result'][0]['hosts'][0]['host']
|
739
|
+
host_id = response['result'][0]['hostid']
|
740
|
+
interface_id = response['result'][0]['interfaceid']
|
741
|
+
|
742
|
+
# ----------------------------------------------------------------------------------
|
743
|
+
|
744
|
+
# Create Item
|
745
|
+
|
746
|
+
if isinstance(item, dict) and utils.isTrue(item, dict):
|
747
|
+
|
748
|
+
logger.info(f'{info} [create item]')
|
749
|
+
|
750
|
+
params = {
|
751
|
+
# 'name': None,
|
752
|
+
# 'key_': None,
|
753
|
+
'hostid': host_id,
|
754
|
+
'type': 7,
|
755
|
+
'value_type': 3,
|
756
|
+
'interfaceid': interface_id,
|
757
|
+
'delay': '1m',
|
758
|
+
'history': '7d',
|
759
|
+
'trends': '7d'
|
760
|
+
} | item
|
761
|
+
|
762
|
+
response = self.request('item.create', params)
|
763
|
+
|
764
|
+
if response is None or response.get('result') is None:
|
765
|
+
continue
|
766
|
+
|
767
|
+
# match True:
|
768
|
+
# case True if utils.isTrue(response, dict) and response.get('result'):
|
769
|
+
# logger.success(f"{log_prefix} success: {response.get('result')}")
|
770
|
+
# case True if utils.isTrue(response, dict) and response.get('error'):
|
771
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
772
|
+
# continue
|
773
|
+
# case True if utils.isTrue(response, utils.NoneType):
|
774
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
775
|
+
# continue
|
776
|
+
# case _:
|
777
|
+
# logger.error(f"{log_prefix} error: {item.get('name')}")
|
778
|
+
# continue
|
779
|
+
|
780
|
+
item_id = response['result']['itemids'][0]
|
781
|
+
|
782
|
+
# ----------------------------------------------------------------------------------
|
783
|
+
|
784
|
+
# Create Trigger
|
785
|
+
|
786
|
+
if isinstance(trigger, dict) and utils.isTrue(trigger, dict):
|
787
|
+
|
788
|
+
logger.info(f'{info} [create trigger]')
|
789
|
+
|
790
|
+
params = {
|
791
|
+
# 'description': None,
|
792
|
+
'priority': '2',
|
793
|
+
# 'expression': None,
|
794
|
+
'manual_close': '1'
|
795
|
+
} | trigger
|
796
|
+
|
797
|
+
# Trigger 的 expression 需要指定 HOST, 例如:
|
798
|
+
# 'last(/DIYCL-110-30/system.uptime)<10m'
|
799
|
+
# DIYCL-110-30 就是 HOST
|
800
|
+
# 但是 HOST 是根据 IP 调用接口获取的, 所以可以写成动态的配置
|
801
|
+
# 'last(/{host}/system.uptime)<10m'.format(host='DIYCL-110-30')
|
802
|
+
# 所以, 传递参数的时候, expression 中就必须要有 {host}, 用于定义 HOST
|
803
|
+
# 如果传递参数的时候使用了 f-strings, 要保留 {host}, 再套一层 {} 即可
|
804
|
+
# f'last(/{{host}}/system.uptime)<10m'
|
805
|
+
params['expression'] = f"{params['expression']}".format(host=host)
|
806
|
+
|
807
|
+
# 注意: create trigger 的 params 的类型是 list
|
808
|
+
response = self.request('trigger.create', [params])
|
809
|
+
|
810
|
+
if response is None or response.get('result') is None:
|
811
|
+
continue
|
812
|
+
|
813
|
+
# logger.warning(f'{log_prefix} response: {response}') if utils.isTrue(self.debug, bool) else next
|
814
|
+
|
815
|
+
# match True:
|
816
|
+
# case True if utils.isTrue(response, dict) and response.get('result'):
|
817
|
+
# logger.success(f"{log_prefix} success: {response.get('result')}")
|
818
|
+
# case True if utils.isTrue(response, dict) and response.get('error'):
|
819
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
820
|
+
# continue
|
821
|
+
# case True if utils.isTrue(response, utils.NoneType):
|
822
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
823
|
+
# continue
|
824
|
+
# case _:
|
825
|
+
# logger.error(f"{log_prefix} error: {trigger.get('name')}")
|
826
|
+
# continue
|
827
|
+
|
828
|
+
# ----------------------------------------------------------------------------------
|
829
|
+
|
830
|
+
# Create Graph
|
831
|
+
|
832
|
+
if utils.isTrue(graph, bool) or (isinstance(graph, dict) and utils.isTrue(graph, dict)):
|
833
|
+
|
834
|
+
log_prefix = 'create graph'
|
835
|
+
|
836
|
+
logger.info(f'{log_prefix} ......')
|
837
|
+
|
838
|
+
# Graph object:
|
839
|
+
#
|
840
|
+
# https://www.zabbix.com/documentation/current/en/manual/api/reference/graph/object
|
841
|
+
#
|
842
|
+
# yaxismax (float) The fixed maximum value for the Y axis.
|
843
|
+
# Default: 100.
|
844
|
+
# yaxismin (float) The fixed minimum value for the Y axis.
|
845
|
+
# Default: 0.
|
846
|
+
# ymax_type (integer) Maximum value calculation method for the Y axis.
|
847
|
+
# Possible values:
|
848
|
+
# 0 - (default) calculated;
|
849
|
+
# 1 - fixed;
|
850
|
+
# 2 - item.
|
851
|
+
# ymin_type (integer) Minimum value calculation method for the Y axis.
|
852
|
+
# Possible values:
|
853
|
+
# 0 - (default) calculated;
|
854
|
+
# 1 - fixed;
|
855
|
+
# 2 - item.
|
856
|
+
#
|
857
|
+
# 'ymin_type': 2,
|
858
|
+
# 'ymin_itemid':item_id,
|
859
|
+
# 'ymax_type': 2,
|
860
|
+
# 'ymax_itemid':item_id,
|
861
|
+
|
862
|
+
if item is None:
|
863
|
+
continue
|
864
|
+
|
865
|
+
params: dict = {
|
866
|
+
'name': item.get('name'),
|
867
|
+
'width': 900,
|
868
|
+
'height': 200,
|
869
|
+
'gitems': [{'itemid': item_id, 'color': '0040FF'}]
|
870
|
+
}
|
871
|
+
|
872
|
+
if isinstance(graph, dict) and utils.isTrue(graph, dict):
|
873
|
+
|
874
|
+
params = params | graph
|
875
|
+
|
876
|
+
if utils.isTrue(params.get('gitems'), list):
|
877
|
+
gitems = params.get('gitems')
|
878
|
+
if gitems is None:
|
879
|
+
continue
|
880
|
+
for gitem in gitems:
|
881
|
+
if isinstance(gitem, dict) and utils.isTrue(gitem, dict) and gitem.get('itemid') == '{}':
|
882
|
+
gitem['itemid'] = item_id
|
883
|
+
|
884
|
+
response = self.request('graph.create', params)
|
885
|
+
|
886
|
+
if response is None:
|
887
|
+
continue
|
888
|
+
|
889
|
+
# logger.warning(f'{log_prefix} response: {response}') if utils.isTrue(self.debug, bool) else next
|
890
|
+
|
891
|
+
# match True:
|
892
|
+
# case True if utils.isTrue(response, dict) and response.get('result'):
|
893
|
+
# logger.success(f"{log_prefix} success: {response.get('result')}")
|
894
|
+
# case True if utils.isTrue(response, dict) and response.get('error'):
|
895
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
896
|
+
# continue
|
897
|
+
# case True if utils.isTrue(response, utils.NoneType):
|
898
|
+
# logger.error(f"{log_prefix} error: {response.get('error', {}).get('data')}")
|
899
|
+
# continue
|
900
|
+
# case _:
|
901
|
+
# logger.error(f"{log_prefix} error: {params.get('name')}")
|
902
|
+
# continue
|
903
|
+
|
904
|
+
# ----------------------------------------------------------------------------------
|
905
|
+
|
906
|
+
logger.success(f"{info} [succeeded]")
|
907
|
+
return True
|
908
|
+
|
909
|
+
except Exception as e:
|
910
|
+
logger.error(f"{info} [failed]")
|
911
|
+
logger.exception(e)
|
912
|
+
return False
|
@@ -4,7 +4,7 @@ ezKit/bottle.py,sha256=43h4v1kzz6qrLvCt5IMN0H-gFtaT0koG9wETqteXsps,181666
|
|
4
4
|
ezKit/bottle_extensions.py,sha256=CwXKxVKxxtbyfeeOSp2xODUqJBo7ro2C88H9sUOVDJI,1161
|
5
5
|
ezKit/cipher.py,sha256=0T_StbjiNI4zgrjVgcfU-ffKgu1waBA9UDudAnqFcNM,2896
|
6
6
|
ezKit/database.py,sha256=r5YNoEzeOeVTlEWI99xXtHTmPZ73_DopS8DTzZk8Lts,12432
|
7
|
-
ezKit/dockerhub.py,sha256=
|
7
|
+
ezKit/dockerhub.py,sha256=a0v9Cdg2vIfUvSSf8gYS22mvQtUcJpJWGGM-nBphrOA,1958
|
8
8
|
ezKit/http.py,sha256=ysXzqXFi9zmuVKINbYGwmf9Q5xDVW_DZWrSh6HSVq8M,1800
|
9
9
|
ezKit/markdown_to_html.template,sha256=21G2sSVGJn6aJvHd0NN4zY5YiDteKe4UtW36AzBwSdk,22274
|
10
10
|
ezKit/mongo.py,sha256=l3jRMmoGrTm16OG4daSCn0JLU1nbYAmTtHokwjLXzoA,2390
|
@@ -14,8 +14,9 @@ ezKit/sendemail.py,sha256=47JTDFoLJKi0YtF3RAp9nFfo0ko2jlde3R_C1wr2E2w,7397
|
|
14
14
|
ezKit/token.py,sha256=HKREyZj_T2S8-aFoFIrBXTaCKExQq4zE66OHXhGHqQg,1750
|
15
15
|
ezKit/utils.py,sha256=uOUOCgx6WU6J2lTbHlL78Flk3oCZgdj8rBOFg2i0K7Q,44241
|
16
16
|
ezKit/xftp.py,sha256=izUH9pLH_AzgR3c0g8xSfhLn7LQ9EDcTst3LFjTM6hU,7878
|
17
|
-
ezKit
|
18
|
-
ezKit-1.11.
|
19
|
-
ezKit-1.11.
|
20
|
-
ezKit-1.11.
|
21
|
-
ezKit-1.11.
|
17
|
+
ezKit/zabbix.py,sha256=JELOKZMXOWSdq5NW-VNJ10vahhRWLAILDGebMi8VunM,33060
|
18
|
+
ezKit-1.11.18.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
19
|
+
ezKit-1.11.18.dist-info/METADATA,sha256=a3t5EbV-TLjAdNm2rx_rAdpRyBqCt3CxgMX3xYsKii0,295
|
20
|
+
ezKit-1.11.18.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
21
|
+
ezKit-1.11.18.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
|
22
|
+
ezKit-1.11.18.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|