pytbox 0.0.2__tar.gz → 0.0.3__tar.gz
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.
Potentially problematic release.
This version of pytbox might be problematic. Click here for more details.
- {pytbox-0.0.2 → pytbox-0.0.3}/PKG-INFO +1 -1
- {pytbox-0.0.2 → pytbox-0.0.3}/pyproject.toml +1 -1
- pytbox-0.0.3/src/pytbox/alert/alert_handler.py +119 -0
- pytbox-0.0.3/src/pytbox/alert/ping.py +25 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/alicloud/sls.py +0 -8
- pytbox-0.0.3/src/pytbox/base.py +41 -0
- pytbox-0.0.3/src/pytbox/database/mongo.py +58 -0
- pytbox-0.0.3/src/pytbox/database/victoriametrics.py +77 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/dida365.py +11 -17
- pytbox-0.0.3/src/pytbox/utils/load_config.py +86 -0
- pytbox-0.0.3/src/pytbox/utils/ping_checker.py +1 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/utils/response.py +1 -1
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/utils/timeutils.py +14 -1
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox.egg-info/PKG-INFO +1 -1
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox.egg-info/SOURCES.txt +9 -3
- pytbox-0.0.2/src/pytbox/victoriametrics.py +0 -37
- {pytbox-0.0.2 → pytbox-0.0.3}/README.md +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/setup.cfg +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/common/__init__.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/common/base.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/feishu/client.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/feishu/endpoints.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/feishu/errors.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/feishu/helpers.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/feishu/typing.py +0 -0
- {pytbox-0.0.2/src/pytbox → pytbox-0.0.3/src/pytbox/log}/logger.py +0 -0
- {pytbox-0.0.2/src/pytbox → pytbox-0.0.3/src/pytbox/log}/victorialog.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/onepassword_connect.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/onepassword_sa.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox/utils/env.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox.egg-info/dependency_links.txt +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox.egg-info/requires.txt +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/src/pytbox.egg-info/top_level.txt +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/tests/test_feishu.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/tests/test_logger.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/tests/test_onepassword_connect.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/tests/test_onepassword_sa.py +0 -0
- {pytbox-0.0.2 → pytbox-0.0.3}/tests/test_victoriametrics.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytbox"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
description = "A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)"
|
|
9
9
|
authors = [{ name = "mingming hou", email = "houm01@foxmail.com" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from ..database.mongo import Mongo
|
|
7
|
+
from ..base import MongoClient
|
|
8
|
+
from ..feishu.client import Client as FeishuClient
|
|
9
|
+
from ..dida365 import Dida365
|
|
10
|
+
from ..utils.timeutils import TimeUtils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AlertHandler:
|
|
14
|
+
|
|
15
|
+
def __init__(self,
|
|
16
|
+
config: dict=None,
|
|
17
|
+
mongo_client: Mongo=MongoClient(collection='alert'),
|
|
18
|
+
feishu_client: FeishuClient=None,
|
|
19
|
+
dida_client: Dida365=None
|
|
20
|
+
):
|
|
21
|
+
self.config = config
|
|
22
|
+
self.mongo = mongo_client
|
|
23
|
+
self.feishu = feishu_client
|
|
24
|
+
self.dida = dida_client
|
|
25
|
+
|
|
26
|
+
def send_alert(self,
|
|
27
|
+
event_id: str=None,
|
|
28
|
+
event_type: Literal['trigger', 'resolved'] ='trigger',
|
|
29
|
+
event_time: str=None,
|
|
30
|
+
event_name: str=None,
|
|
31
|
+
event_content: str=None,
|
|
32
|
+
entity_name: str=None,
|
|
33
|
+
priority: Literal['critical', 'high', 'warning']='high',
|
|
34
|
+
resolved_expr: str=None,
|
|
35
|
+
suggestion: str='',
|
|
36
|
+
troubleshot: str='暂无',
|
|
37
|
+
mongo_id: str=None
|
|
38
|
+
):
|
|
39
|
+
|
|
40
|
+
if not event_id:
|
|
41
|
+
event_id = str(uuid.uuid4())
|
|
42
|
+
if not event_time:
|
|
43
|
+
event_time = TimeUtils.get_now_time_mongo()
|
|
44
|
+
|
|
45
|
+
if self.mongo.check_alarm_exist(event_type=event_type, event_content=event_content):
|
|
46
|
+
if event_type == "trigger":
|
|
47
|
+
self.mongo.collection.insert_one(
|
|
48
|
+
{
|
|
49
|
+
'event_id': event_id,
|
|
50
|
+
'event_type': event_type,
|
|
51
|
+
'event_name': event_name,
|
|
52
|
+
'event_time': event_time,
|
|
53
|
+
'event_content': event_content,
|
|
54
|
+
'entity_name': entity_name,
|
|
55
|
+
'priority': priority,
|
|
56
|
+
'resolved_expr': resolved_expr,
|
|
57
|
+
'suggestion': suggestion,
|
|
58
|
+
'troubleshot': troubleshot,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
elif event_type == "resolved":
|
|
62
|
+
filter_doc = {"_id": mongo_id}
|
|
63
|
+
update = {"$set": { "resolved_time": event_time}}
|
|
64
|
+
self.mongo.collection.update_one(filter_doc, update)
|
|
65
|
+
alarm_time = self.mongo.collection.find_one(filter_doc, {'event_time': 1})['event_time']
|
|
66
|
+
|
|
67
|
+
content = [
|
|
68
|
+
f'**事件名称**: {event_name}',
|
|
69
|
+
f'**告警时间**: {TimeUtils.convert_timeobj_to_str(timeobj=event_time, timezone_offset=0) if event_type == "trigger" else TimeUtils.convert_timeobj_to_str(timeobj=alarm_time, timezone_offset=8)}',
|
|
70
|
+
f'**事件内容**: {event_content}',
|
|
71
|
+
f'**告警实例**: {entity_name}',
|
|
72
|
+
f'**建议**: {suggestion}',
|
|
73
|
+
f'**故障排查**: {troubleshot}'
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
if event_type == "resolved":
|
|
77
|
+
content.insert(2, f'**恢复时间**: {TimeUtils.convert_timeobj_to_str(event_time, timezone_offset=0)}')
|
|
78
|
+
|
|
79
|
+
if self.config['feishu']['enable_alert']:
|
|
80
|
+
self.feishu.extensions.send_message_notify(
|
|
81
|
+
color='red' if event_type == "trigger" else 'green',
|
|
82
|
+
title=event_content,
|
|
83
|
+
priority=priority,
|
|
84
|
+
sub_title="",
|
|
85
|
+
content='\n'.join(content)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if self.config['dida']['enable_alert']:
|
|
89
|
+
if event_type == "trigger":
|
|
90
|
+
res = self.dida.task_create(
|
|
91
|
+
|
|
92
|
+
project_id=self.config['dida']['alert_project_id'],
|
|
93
|
+
title=event_content,
|
|
94
|
+
content='\n'.join(content),
|
|
95
|
+
tags=['L-监控告警', priority]
|
|
96
|
+
)
|
|
97
|
+
dida_task_id = res.data.get("id")
|
|
98
|
+
self.mongo.collection.update_one(
|
|
99
|
+
{
|
|
100
|
+
"event_id": event_id
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"$set": {
|
|
104
|
+
"dida_task_id": dida_task_id
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
task_id = self.mongo.collection.find_one(filter_doc, {'dida_task_id': 1})['dida_task_id']
|
|
110
|
+
self.dida.task_update(
|
|
111
|
+
task_id=task_id,
|
|
112
|
+
project_id=self.config['dida']['alert_project_id'],
|
|
113
|
+
content=f'\n**恢复时间**: {TimeUtils.convert_timeobj_to_str(timeobj=event_time, timezone_offset=0)}'
|
|
114
|
+
)
|
|
115
|
+
self.dida.task_complete(task_id=task_id, project_id=self.config['dida']['alert_project_id'])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if self.config['wecom']['enable']:
|
|
119
|
+
pass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from ..database.victoriametrics import VictoriaMetrics
|
|
5
|
+
from ..lib.load_config import load_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ping(config, target):
|
|
9
|
+
vm = VictoriaMetrics(url=config['victoriametrics']['url'])
|
|
10
|
+
ping_status = vm.query_ping_status(target=target, last_minutes=config['alert']['ping']['last_minutes'])
|
|
11
|
+
if ping_status == '不通':
|
|
12
|
+
insert_alert(
|
|
13
|
+
event_name=config['alert']['ping']['event_name'],
|
|
14
|
+
event_content=f"{target} {config['alert']['ping']['event_name']}",
|
|
15
|
+
entity_name=target,
|
|
16
|
+
priority=config['alert']['ping']['priority'],
|
|
17
|
+
resolved_query={
|
|
18
|
+
"target": target
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
config = load_config()
|
|
25
|
+
ping(config, "10.30.35.38")
|
|
@@ -20,14 +20,6 @@ class AliCloudSls:
|
|
|
20
20
|
self.logstore = logstore
|
|
21
21
|
|
|
22
22
|
def get_logs(self, project_name, logstore_name, query, from_time, to_time):
|
|
23
|
-
# Project名称。
|
|
24
|
-
# project_name = "sh-prod-network-devices-log"
|
|
25
|
-
# Logstore名称
|
|
26
|
-
# logstore_name = "sh-prod-network-devices-log"
|
|
27
|
-
# 查询语句。
|
|
28
|
-
# query = "*| select dev,id from " + logstore_name
|
|
29
|
-
# query = "*"
|
|
30
|
-
# 索引。
|
|
31
23
|
logstore_index = {'line': {
|
|
32
24
|
'token': [',', ' ', "'", '"', ';', '=', '(', ')', '[', ']', '{', '}', '?', '@', '&', '<', '>', '/', ':', '\n', '\t',
|
|
33
25
|
'\r'], 'caseSensitive': False, 'chn': False}, 'keys': {'dev': {'type': 'text',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from pytbox.database.mongo import Mongo
|
|
5
|
+
from pytbox.utils.load_config import load_config_by_file
|
|
6
|
+
from pytbox.database.victoriametrics import VictoriaMetrics
|
|
7
|
+
from pytbox.feishu.client import Client as FeishuClient
|
|
8
|
+
from pytbox.dida365 import Dida365
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def MongoClient(collection, config_path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=None):
|
|
12
|
+
config = load_config_by_file(path=config_path, oc_vault_id=oc_vault_id)
|
|
13
|
+
return Mongo(
|
|
14
|
+
host=config['mongo']['host'],
|
|
15
|
+
port=config['mongo']['port'],
|
|
16
|
+
username=config['mongo']['username'],
|
|
17
|
+
password=config['mongo']['password'],
|
|
18
|
+
auto_source=config['mongo']['auto_source'],
|
|
19
|
+
db_name=config['mongo']['db_name'],
|
|
20
|
+
collection=collection
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def vm_client(config_path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=None):
|
|
24
|
+
config = load_config_by_file(path=config_path, oc_vault_id=oc_vault_id)
|
|
25
|
+
return VictoriaMetrics(
|
|
26
|
+
url=config['victoriametrics']['url']
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def feishu_client(config_path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=None):
|
|
30
|
+
config = load_config_by_file(path=config_path, oc_vault_id=oc_vault_id)
|
|
31
|
+
return FeishuClient(
|
|
32
|
+
app_id=config['feishu']['app_id'],
|
|
33
|
+
app_secret=config['feishu']['app_secret']
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def dida_client(config_path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=None):
|
|
37
|
+
config = load_config_by_file(path=config_path, oc_vault_id=oc_vault_id)
|
|
38
|
+
return Dida365(
|
|
39
|
+
cookie=config['dida']['cookie'],
|
|
40
|
+
access_token=config['dida']['access_token']
|
|
41
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import pymongo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Mongo:
|
|
7
|
+
'''
|
|
8
|
+
当前主要使用的类
|
|
9
|
+
'''
|
|
10
|
+
def __init__(self, host, port, username, password, auto_source, db_name: str='automate', collection: str=None):
|
|
11
|
+
self.client = self._create_client(host, port, username, password, auto_source)
|
|
12
|
+
self.collection = self.client[db_name][collection]
|
|
13
|
+
|
|
14
|
+
def _create_client(self, host, port, username, password, auto_source):
|
|
15
|
+
'''
|
|
16
|
+
创建客户端
|
|
17
|
+
'''
|
|
18
|
+
return pymongo.MongoClient(host=host,
|
|
19
|
+
port=port,
|
|
20
|
+
username=username,
|
|
21
|
+
password=password,
|
|
22
|
+
authSource=auto_source)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_alarm_exist(self, event_type, event_content) -> bool:
|
|
26
|
+
'''
|
|
27
|
+
_summary_
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
event_content (_type_): 告警内容
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
bool: 如果为 True, 表示允许插入告警
|
|
34
|
+
'''
|
|
35
|
+
if event_type == 'trigger':
|
|
36
|
+
query = { "event_content": event_content }
|
|
37
|
+
fields = {"event_name": 1, "event_time": 1, "resolved_time": 1}
|
|
38
|
+
result = self.collection.find(query, fields).sort({ "_id": pymongo.DESCENDING }).limit(1)
|
|
39
|
+
if self.collection.count_documents(query) == 0:
|
|
40
|
+
return True
|
|
41
|
+
else:
|
|
42
|
+
for doc in result:
|
|
43
|
+
if 'resolved_time' in doc:
|
|
44
|
+
# 当前没有告警, 可以插入数据
|
|
45
|
+
return True
|
|
46
|
+
elif event_type == 'resolved':
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def query_alert_not_resolved(self, event_name: str=None):
|
|
50
|
+
query = {
|
|
51
|
+
"$or": [
|
|
52
|
+
{"resolved_time": { "$exists": False }}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
if event_name:
|
|
56
|
+
query['event_name'] = event_name
|
|
57
|
+
return self.collection.find(query)
|
|
58
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from ..utils.response import ReturnResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class VictoriaMetrics:
|
|
8
|
+
|
|
9
|
+
def __init__(self, url: str='', timeout: int=3) -> None:
|
|
10
|
+
self.url = url
|
|
11
|
+
self.timeout = timeout
|
|
12
|
+
self.session = requests.Session()
|
|
13
|
+
self.session.headers.update({
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
'Accept': 'application/json'
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
def query(self, query: str) -> ReturnResponse:
|
|
19
|
+
'''
|
|
20
|
+
查询指标数据
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
query (str): 查询语句
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict: 查询结果
|
|
27
|
+
'''
|
|
28
|
+
url = f"{self.url}/prometheus/api/v1/query"
|
|
29
|
+
r = requests.get(
|
|
30
|
+
url,
|
|
31
|
+
timeout=self.timeout,
|
|
32
|
+
params={"query": query}
|
|
33
|
+
)
|
|
34
|
+
if r.json().get("status") == "success":
|
|
35
|
+
if r.json()['data']['result']:
|
|
36
|
+
return ReturnResponse(code=0, msg=f"[{query}] 查询成功!", data=r.json()['data']['result'])
|
|
37
|
+
else:
|
|
38
|
+
return ReturnResponse(code=2, msg=f"[{query}] 没有查询到结果", data=r.json())
|
|
39
|
+
else:
|
|
40
|
+
return ReturnResponse(code=1, msg=f"[{query}] 查询失败: {r.json().get('error')}", data=r.json())
|
|
41
|
+
|
|
42
|
+
def check_ping_result(self, target: str, last_minute: int=10) -> ReturnResponse:
|
|
43
|
+
'''
|
|
44
|
+
检查ping结果
|
|
45
|
+
'''
|
|
46
|
+
if target:
|
|
47
|
+
# 这里需要在字符串中保留 {},同时插入 target,可以用双大括号转义
|
|
48
|
+
query = f"ping_result_code{{target='{target}'}}"
|
|
49
|
+
else:
|
|
50
|
+
query = "ping_result_code"
|
|
51
|
+
|
|
52
|
+
if last_minute:
|
|
53
|
+
query = query + f"[{last_minute}m]"
|
|
54
|
+
|
|
55
|
+
r = self.query(query=query)
|
|
56
|
+
if r.code == 0:
|
|
57
|
+
values = r.data[0]['values']
|
|
58
|
+
if len(values) == 2 and values[1] == "0":
|
|
59
|
+
code = 0
|
|
60
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
61
|
+
else:
|
|
62
|
+
if all(str(item[1]) == "1" for item in values):
|
|
63
|
+
code = 2
|
|
64
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
|
|
65
|
+
else:
|
|
66
|
+
code = 0
|
|
67
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
68
|
+
elif r.code == 2:
|
|
69
|
+
code = 2
|
|
70
|
+
msg = f"没有查询到 {target} 最近 {last_minute} 分钟的ping结果!"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
data = r.data[0]
|
|
74
|
+
except KeyError:
|
|
75
|
+
data = r.data
|
|
76
|
+
|
|
77
|
+
return ReturnResponse(code=code, msg=msg, data=data)
|
|
@@ -28,14 +28,8 @@ class Task:
|
|
|
28
28
|
completed_time: str
|
|
29
29
|
assignee: int
|
|
30
30
|
|
|
31
|
-
@dataclass
|
|
32
|
-
class DidaResponse:
|
|
33
|
-
code: int
|
|
34
|
-
message: str
|
|
35
|
-
data: dict
|
|
36
|
-
|
|
37
31
|
|
|
38
|
-
class
|
|
32
|
+
class ProcessReturnResponse:
|
|
39
33
|
@staticmethod
|
|
40
34
|
def status(status):
|
|
41
35
|
if status == 0:
|
|
@@ -89,7 +83,7 @@ class Dida365:
|
|
|
89
83
|
}
|
|
90
84
|
self.timeout = 10
|
|
91
85
|
|
|
92
|
-
def request(self, api_url: str=None, method: str='GET', payload: dict={}):
|
|
86
|
+
def request(self, api_url: str=None, method: str='GET', payload: dict={}) -> ReturnResponse:
|
|
93
87
|
"""发送请求。
|
|
94
88
|
|
|
95
89
|
Args:
|
|
@@ -108,14 +102,14 @@ class Dida365:
|
|
|
108
102
|
|
|
109
103
|
if response.status_code == 200:
|
|
110
104
|
if 'complete' in api_url:
|
|
111
|
-
return
|
|
105
|
+
return ReturnResponse(code=0, msg='success', data=None)
|
|
112
106
|
else:
|
|
113
107
|
try:
|
|
114
|
-
return
|
|
115
|
-
except Exception
|
|
116
|
-
return
|
|
108
|
+
return ReturnResponse(code=0, msg='success', data=response.json())
|
|
109
|
+
except Exception:
|
|
110
|
+
return ReturnResponse(code=1, msg='warning', data=response)
|
|
117
111
|
else:
|
|
118
|
-
return
|
|
112
|
+
return ReturnResponse(code=1, msg='error', data=response.json())
|
|
119
113
|
|
|
120
114
|
def task_list(self, project_id: str, enhancement: bool=True):
|
|
121
115
|
"""获取任务列表。
|
|
@@ -142,8 +136,8 @@ class Dida365:
|
|
|
142
136
|
desc=task.get('desc'),
|
|
143
137
|
start_date=task.get('startDate'),
|
|
144
138
|
due_date=task.get('dueDate'),
|
|
145
|
-
priority=
|
|
146
|
-
status=
|
|
139
|
+
priority=ProcessReturnResponse.priority(task.get('priority')),
|
|
140
|
+
status=ProcessReturnResponse.status(task.get('status')),
|
|
147
141
|
tags=task.get('tags'),
|
|
148
142
|
completed_time=task.get('completedTime'),
|
|
149
143
|
assignee=task.get('assignee'))
|
|
@@ -157,8 +151,8 @@ class Dida365:
|
|
|
157
151
|
desc=task.get('desc'),
|
|
158
152
|
start_date=task.get('startDate'),
|
|
159
153
|
due_date=task.get('dueDate'),
|
|
160
|
-
priority=
|
|
161
|
-
status=
|
|
154
|
+
priority=ProcessReturnResponse.priority(task.get('priority')),
|
|
155
|
+
status=ProcessReturnResponse.status(task.get('status')),
|
|
162
156
|
tags=task.get('tags'),
|
|
163
157
|
completed_time=task.get('completedTime'),
|
|
164
158
|
assignee=task.get('assignee'))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import toml
|
|
6
|
+
except ImportError:
|
|
7
|
+
import tomllib as toml # Python 3.11+ fallback
|
|
8
|
+
from pytbox.onepassword_connect import OnePasswordConnect
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _replace_1password_values(data, oc):
|
|
12
|
+
"""
|
|
13
|
+
递归处理配置数据,替换 1password 和 password 开头的值
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
data: 配置数据(dict, list, str 等)
|
|
17
|
+
oc: OnePasswordConnect 实例
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
处理后的数据
|
|
21
|
+
"""
|
|
22
|
+
if isinstance(data, dict):
|
|
23
|
+
result = {}
|
|
24
|
+
for k, v in data.items():
|
|
25
|
+
result[k] = _replace_1password_values(v, oc)
|
|
26
|
+
return result
|
|
27
|
+
elif isinstance(data, list):
|
|
28
|
+
return [_replace_1password_values(item, oc) for item in data]
|
|
29
|
+
elif isinstance(data, str):
|
|
30
|
+
# 处理 1password,item_id,field_name 格式
|
|
31
|
+
if data.startswith("1password,"):
|
|
32
|
+
parts = data.split(",")
|
|
33
|
+
if len(parts) >= 3:
|
|
34
|
+
item_id = parts[1]
|
|
35
|
+
field_name = parts[2]
|
|
36
|
+
# 通过 item_id 获取项目,然后从字段中提取对应值
|
|
37
|
+
item = oc.get_item(item_id)
|
|
38
|
+
for field in item.fields:
|
|
39
|
+
if field.label == field_name:
|
|
40
|
+
return field.value
|
|
41
|
+
return data # 如果找不到字段,返回原始值
|
|
42
|
+
# 处理 password,item_id,field_name 格式
|
|
43
|
+
elif data.startswith("password,"):
|
|
44
|
+
parts = data.split(",")
|
|
45
|
+
if len(parts) >= 3:
|
|
46
|
+
item_id = parts[1]
|
|
47
|
+
field_name = parts[2]
|
|
48
|
+
# 通过 item_id 获取项目,然后从字段中提取对应值
|
|
49
|
+
item = oc.get_item(item_id)
|
|
50
|
+
for field in item.fields:
|
|
51
|
+
if field.label == field_name:
|
|
52
|
+
return field.value
|
|
53
|
+
return data # 如果找不到字段,返回原始值
|
|
54
|
+
return data
|
|
55
|
+
else:
|
|
56
|
+
return data
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_config_by_file(path: str='/workspaces/pytbox/src/pytbox/alert/config/config.toml', oc_vault_id: str=None) -> dict:
|
|
60
|
+
'''
|
|
61
|
+
从文件加载配置,支持 1password 集成
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
path (str, optional): 配置文件路径. Defaults to '/workspaces/pytbox/src/pytbox/alert/config/config.toml'.
|
|
65
|
+
oc_vault_id (str, optional): OnePassword vault ID,如果提供则启用 1password 集成
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
dict: 配置字典
|
|
69
|
+
'''
|
|
70
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
71
|
+
if path.endswith('.toml'):
|
|
72
|
+
config = toml.load(f)
|
|
73
|
+
else:
|
|
74
|
+
# 如果不是 toml 文件,假设是其他格式,这里可以扩展
|
|
75
|
+
import json
|
|
76
|
+
config = json.load(f)
|
|
77
|
+
|
|
78
|
+
if oc_vault_id:
|
|
79
|
+
oc = OnePasswordConnect(vault_id=oc_vault_id)
|
|
80
|
+
config = _replace_1password_values(config, oc)
|
|
81
|
+
|
|
82
|
+
return config
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
print(load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id="hcls5uxuq5dmxorw6rfewefdsa"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import time
|
|
5
5
|
import pytz
|
|
6
6
|
import datetime
|
|
7
|
+
from typing import Literal
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class TimeUtils:
|
|
@@ -37,4 +38,16 @@ class TimeUtils:
|
|
|
37
38
|
|
|
38
39
|
@staticmethod
|
|
39
40
|
def get_utc_time():
|
|
40
|
-
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
41
|
+
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def get_now_time_mongo():
|
|
45
|
+
return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def convert_timeobj_to_str(timeobj: str=None, timezone_offset: int=8, time_format: Literal['%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%SZ']='%Y-%m-%d %H:%M:%S'):
|
|
49
|
+
time_obj_with_offset = timeobj + datetime.timedelta(hours=timezone_offset)
|
|
50
|
+
if time_format == '%Y-%m-%d %H:%M:%S':
|
|
51
|
+
return time_obj_with_offset.strftime("%Y-%m-%d %H:%M:%S")
|
|
52
|
+
elif time_format == '%Y-%m-%dT%H:%M:%SZ':
|
|
53
|
+
return time_obj_with_offset.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
|
+
src/pytbox/base.py
|
|
3
4
|
src/pytbox/dida365.py
|
|
4
|
-
src/pytbox/logger.py
|
|
5
5
|
src/pytbox/onepassword_connect.py
|
|
6
6
|
src/pytbox/onepassword_sa.py
|
|
7
|
-
src/pytbox/victorialog.py
|
|
8
|
-
src/pytbox/victoriametrics.py
|
|
9
7
|
src/pytbox.egg-info/PKG-INFO
|
|
10
8
|
src/pytbox.egg-info/SOURCES.txt
|
|
11
9
|
src/pytbox.egg-info/dependency_links.txt
|
|
12
10
|
src/pytbox.egg-info/requires.txt
|
|
13
11
|
src/pytbox.egg-info/top_level.txt
|
|
12
|
+
src/pytbox/alert/alert_handler.py
|
|
13
|
+
src/pytbox/alert/ping.py
|
|
14
14
|
src/pytbox/alicloud/sls.py
|
|
15
15
|
src/pytbox/common/__init__.py
|
|
16
16
|
src/pytbox/common/base.py
|
|
17
|
+
src/pytbox/database/mongo.py
|
|
18
|
+
src/pytbox/database/victoriametrics.py
|
|
17
19
|
src/pytbox/feishu/client.py
|
|
18
20
|
src/pytbox/feishu/endpoints.py
|
|
19
21
|
src/pytbox/feishu/errors.py
|
|
20
22
|
src/pytbox/feishu/helpers.py
|
|
21
23
|
src/pytbox/feishu/typing.py
|
|
24
|
+
src/pytbox/log/logger.py
|
|
25
|
+
src/pytbox/log/victorialog.py
|
|
22
26
|
src/pytbox/utils/env.py
|
|
27
|
+
src/pytbox/utils/load_config.py
|
|
28
|
+
src/pytbox/utils/ping_checker.py
|
|
23
29
|
src/pytbox/utils/response.py
|
|
24
30
|
src/pytbox/utils/timeutils.py
|
|
25
31
|
tests/test_feishu.py
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import requests
|
|
4
|
-
from .utils.response import ReturnResponse
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class VictoriaMetrics:
|
|
8
|
-
|
|
9
|
-
def __init__(self, url: str='', timeout: int=3) -> None:
|
|
10
|
-
self.url = url
|
|
11
|
-
self.timeout = timeout
|
|
12
|
-
self.session = requests.Session()
|
|
13
|
-
self.session.headers.update({
|
|
14
|
-
'Content-Type': 'application/json',
|
|
15
|
-
'Accept': 'application/json'
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
def query(self, query: str) -> ReturnResponse:
|
|
19
|
-
'''
|
|
20
|
-
查询指标数据
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
query (str): 查询语句
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
dict: 查询结果
|
|
27
|
-
'''
|
|
28
|
-
url = f"{self.url}/prometheus/api/v1/query"
|
|
29
|
-
r = requests.get(
|
|
30
|
-
url,
|
|
31
|
-
timeout=self.timeout,
|
|
32
|
-
params={"query": query}
|
|
33
|
-
)
|
|
34
|
-
if r.json().get("status") == "success":
|
|
35
|
-
return ReturnResponse(code=0, msg=f"[{query}] 查询成功!", data=r.json()['data']['result'])
|
|
36
|
-
else:
|
|
37
|
-
return ReturnResponse(code=1, msg=f"[{query}] 查询失败: {r.json().get('error')}", data=r.json())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|