dts-dance 0.2.4__py3-none-any.whl → 0.2.6__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.
- {dts_dance-0.2.4.dist-info → dts_dance-0.2.6.dist-info}/METADATA +2 -2
- dts_dance-0.2.6.dist-info/RECORD +15 -0
- dtsdance/bytecloud.py +8 -3
- dtsdance/ddutil.py +52 -0
- dtsdance/dflow.py +50 -54
- dtsdance/dsyncer.py +15 -51
- dtsdance/feishu_base.py +44 -7
- dtsdance/{spacex_bytedts.py → spacex.py} +8 -8
- dtsdance/tcc_inner.py +4 -4
- dtsdance/tcc_open.py +1 -1
- dts_dance-0.2.4.dist-info/RECORD +0 -14
- {dts_dance-0.2.4.dist-info → dts_dance-0.2.6.dist-info}/WHEEL +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dts-dance
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: dts dance lib
|
|
5
5
|
Keywords: observation,tools
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: boto3
|
|
7
|
+
Requires-Dist: boto3>=1.42.35
|
|
8
8
|
Requires-Dist: loguru<0.8.0,>=0.7.3
|
|
9
9
|
Requires-Dist: pyyaml<7.0.0,>=6.0.3
|
|
10
10
|
Requires-Dist: requests-toolbelt==1.0.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
|
|
2
|
+
dtsdance/bytecloud.py,sha256=LZ-uw5NMmapyNpt89bMKhCgv4U2cGR-XjLzFFFEXGxM,5899
|
|
3
|
+
dtsdance/ddutil.py,sha256=1ZUmW11nw8WW1NfAIRbjuKy8zeQVTFvoXl6QY3o4YJ0,1534
|
|
4
|
+
dtsdance/dflow.py,sha256=jwsngCqgHUSAUFh-IUgq-fnQs8lAvHtj7uLLs_tupqI,7718
|
|
5
|
+
dtsdance/dsyncer.py,sha256=PCatQIFXgi1l2qT60zNN-ALAqtv7_sg-D2oNMB_t8qw,9906
|
|
6
|
+
dtsdance/feishu_base.py,sha256=dYYNnsCjGZYN2I6inR9FRQoUiDLB7j40rzydw50N38o,11825
|
|
7
|
+
dtsdance/feishu_table.py,sha256=JleCjQJ-rcbC15v2UAc5BK-YioB2SMGsdGztQ9BaT4k,10779
|
|
8
|
+
dtsdance/metrics_fe.py,sha256=hzIl5BJmuCrkqJOHELVzXm3YAqrPttbyVkKBglS4mgQ,18978
|
|
9
|
+
dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
|
|
10
|
+
dtsdance/spacex.py,sha256=W_YGt_Wo_Cy4eDhgXhNfskayPHcZ1kg2Mil2ghI_VLE,4791
|
|
11
|
+
dtsdance/tcc_inner.py,sha256=QqVyhwP0kCdp1HFHFgtme9o0im-eQNn1HVGdLt3Xr8g,1627
|
|
12
|
+
dtsdance/tcc_open.py,sha256=JIfVhL3yfm0ftS_vzONE2QNzfqcZuX1mPTvjovAtwmE,6893
|
|
13
|
+
dts_dance-0.2.6.dist-info/METADATA,sha256=cUaSHfyZVfa3EzpzyvyIJuiot0s1e166G3Omzzk3vMU,826
|
|
14
|
+
dts_dance-0.2.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
dts_dance-0.2.6.dist-info/RECORD,,
|
dtsdance/bytecloud.py
CHANGED
|
@@ -145,18 +145,23 @@ class ByteCloudClient:
|
|
|
145
145
|
site_config = self.get_site_config(site)
|
|
146
146
|
return self._acquire_jwt_token(site_config.endpoint, site_config.svc_secret)
|
|
147
147
|
|
|
148
|
-
def build_request_headers(self, site: str) -> dict[str, str]:
|
|
148
|
+
def build_request_headers(self, site: str, vregion: str | None = None) -> dict[str, str]:
|
|
149
149
|
"""
|
|
150
150
|
构建请求头
|
|
151
151
|
|
|
152
152
|
Args:
|
|
153
153
|
site: 站点名称
|
|
154
|
-
|
|
154
|
+
vregion: 默认为 None
|
|
155
155
|
Returns:
|
|
156
156
|
dict[str, str]: 请求头字典
|
|
157
157
|
"""
|
|
158
158
|
jwt_token = self.get_jwt_token(site)
|
|
159
|
-
|
|
159
|
+
headers = {"Content-Type": "application/json", "x-jwt-token": jwt_token}
|
|
160
|
+
if vregion:
|
|
161
|
+
headers["x-bcgw-vregion"] = vregion
|
|
162
|
+
headers["get-svc"] = "1" # response header 中显示实际请求的机房
|
|
163
|
+
|
|
164
|
+
return headers
|
|
160
165
|
|
|
161
166
|
def get_site_config(self, site: str) -> SiteConfig:
|
|
162
167
|
"""
|
dtsdance/ddutil.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from loguru import logger
|
|
3
|
+
import requests
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def make_request(method: str, url: str, headers: dict[str, str], payload: Optional[dict] = None) -> dict[str, Any]:
|
|
8
|
+
"""
|
|
9
|
+
发送 HTTP 请求的通用方法
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
method: HTTP 方法 (GET/POST)
|
|
13
|
+
url: 请求 URL
|
|
14
|
+
headers: 请求头
|
|
15
|
+
payload: POST 请求的 JSON 数据
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
dict[str, Any]: 解析后的 JSON 响应
|
|
19
|
+
"""
|
|
20
|
+
response = None
|
|
21
|
+
try:
|
|
22
|
+
if method.upper() == "GET":
|
|
23
|
+
response = requests.get(url, headers=headers)
|
|
24
|
+
elif method.upper() == "POST":
|
|
25
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
28
|
+
|
|
29
|
+
# 检查响应状态码
|
|
30
|
+
# response.raise_for_status()
|
|
31
|
+
|
|
32
|
+
# 解析 JSON 响应
|
|
33
|
+
return response.json()
|
|
34
|
+
|
|
35
|
+
except Exception as e:
|
|
36
|
+
error_msg = f"make_request occur error, error: {e}"
|
|
37
|
+
if response is not None:
|
|
38
|
+
error_msg += f", response.text: {response.text}"
|
|
39
|
+
logger.warning(error_msg)
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def output_curl(url: str, headers: dict[str, str], payload: dict) -> str:
|
|
44
|
+
"""
|
|
45
|
+
生成等效的 curl 命令字符串
|
|
46
|
+
"""
|
|
47
|
+
curl_headers = " \\\n ".join([f"-H '{k}: {v}'" for k, v in headers.items()])
|
|
48
|
+
curl_payload = json.dumps(payload, ensure_ascii=False)
|
|
49
|
+
curl_cmd = f"""curl -X POST '{url}' \\
|
|
50
|
+
{curl_headers} \\
|
|
51
|
+
-d '{curl_payload}'"""
|
|
52
|
+
return curl_cmd
|
dtsdance/dflow.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import collections
|
|
2
|
+
from typing import Any
|
|
2
3
|
from loguru import logger
|
|
4
|
+
|
|
5
|
+
from dtsdance.ddutil import make_request, output_curl
|
|
3
6
|
from .bytecloud import ByteCloudClient
|
|
4
|
-
import requests
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class TaskNotFound(Exception):
|
|
@@ -17,42 +19,7 @@ class DFlowClient:
|
|
|
17
19
|
def __init__(self, bytecloud_client: ByteCloudClient) -> None:
|
|
18
20
|
self.bytecloud_client = bytecloud_client
|
|
19
21
|
|
|
20
|
-
def
|
|
21
|
-
"""
|
|
22
|
-
发送 HTTP 请求的通用方法
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
method: HTTP 方法 (GET/POST)
|
|
26
|
-
url: 请求 URL
|
|
27
|
-
headers: 请求头
|
|
28
|
-
json_data: POST 请求的 JSON 数据
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
dict[str, Any]: 解析后的 JSON 响应
|
|
32
|
-
"""
|
|
33
|
-
response = None
|
|
34
|
-
try:
|
|
35
|
-
if method.upper() == "GET":
|
|
36
|
-
response = requests.get(url, headers=headers)
|
|
37
|
-
elif method.upper() == "POST":
|
|
38
|
-
response = requests.post(url, json=json_data, headers=headers)
|
|
39
|
-
else:
|
|
40
|
-
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
41
|
-
|
|
42
|
-
# 检查响应状态码
|
|
43
|
-
response.raise_for_status()
|
|
44
|
-
|
|
45
|
-
# 解析 JSON 响应
|
|
46
|
-
return response.json()
|
|
47
|
-
|
|
48
|
-
except Exception as e:
|
|
49
|
-
error_msg = f"_make_request occur error, error: {e}"
|
|
50
|
-
if response is not None:
|
|
51
|
-
error_msg += f", response.text: {response.text}"
|
|
52
|
-
logger.warning(error_msg)
|
|
53
|
-
raise
|
|
54
|
-
|
|
55
|
-
def get_task_info(self, site: str, task_id: str) -> dict[str, Any]:
|
|
22
|
+
def get_task_info(self, site: str, task_id: str, vregion: str | None = None) -> dict[str, Any]:
|
|
56
23
|
"""
|
|
57
24
|
获取 DFlow 任务信息
|
|
58
25
|
|
|
@@ -68,9 +35,9 @@ class DFlowClient:
|
|
|
68
35
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
|
|
69
36
|
|
|
70
37
|
# 构建请求数据
|
|
71
|
-
|
|
38
|
+
payload = {"id": int(task_id)}
|
|
72
39
|
|
|
73
|
-
response_data =
|
|
40
|
+
response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, vregion), payload)
|
|
74
41
|
|
|
75
42
|
message = response_data.get("message")
|
|
76
43
|
# logger.debug(f"get_task_info {site} {task_id}, message: {message}")
|
|
@@ -79,8 +46,7 @@ class DFlowClient:
|
|
|
79
46
|
raise TaskNotFound(f"获取 DFlow 任务信息失败,站点: {site}, 任务 ID: {task_id} 不存在")
|
|
80
47
|
|
|
81
48
|
try:
|
|
82
|
-
|
|
83
|
-
task = cast(dict, data.get("task", {}))
|
|
49
|
+
task = response_data.get("data", {}).get("task", {})
|
|
84
50
|
# 提取核心信息
|
|
85
51
|
filtered_data = {
|
|
86
52
|
"task_id": task.get("id", ""),
|
|
@@ -94,7 +60,7 @@ class DFlowClient:
|
|
|
94
60
|
except (KeyError, AttributeError, Exception) as e:
|
|
95
61
|
raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
|
|
96
62
|
|
|
97
|
-
def get_dflow_info(self, site: str, dflow_id: str) -> dict[str, Any]:
|
|
63
|
+
def get_dflow_info(self, site: str, dflow_id: str, vregion: str | None = None) -> dict[str, Any]:
|
|
98
64
|
"""
|
|
99
65
|
获取 DFlow 进程信息
|
|
100
66
|
|
|
@@ -110,9 +76,9 @@ class DFlowClient:
|
|
|
110
76
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeDFlowDetail"
|
|
111
77
|
|
|
112
78
|
# 构建请求数据
|
|
113
|
-
|
|
79
|
+
payload = {"dflow_id": int(dflow_id)}
|
|
114
80
|
|
|
115
|
-
response_data =
|
|
81
|
+
response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, vregion), payload)
|
|
116
82
|
|
|
117
83
|
message = response_data.get("message", "")
|
|
118
84
|
# logger.debug(f"get_dflow_info {site} {dflow_id}, message: {message}")
|
|
@@ -121,8 +87,7 @@ class DFlowClient:
|
|
|
121
87
|
raise DFlowNotFound(f"获取 DFlow 进程信息失败,站点: {site}, 进程 ID: {dflow_id} 不存在")
|
|
122
88
|
|
|
123
89
|
try:
|
|
124
|
-
|
|
125
|
-
dflow = cast(dict, data.get("dflow", {}))
|
|
90
|
+
dflow = response_data.get("data", {}).get("dflow", {})
|
|
126
91
|
# 提取核心信息
|
|
127
92
|
filtered_data = {
|
|
128
93
|
"dflow_id": dflow.get("id", ""),
|
|
@@ -168,23 +133,23 @@ class DFlowClient:
|
|
|
168
133
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
|
|
169
134
|
|
|
170
135
|
# 构建请求数据
|
|
171
|
-
|
|
136
|
+
payload = {"ctrl_env": ctrl_env}
|
|
172
137
|
|
|
173
|
-
response_data =
|
|
138
|
+
response_data = make_request("POST", url, self.bytecloud_client.build_request_headers(site, None), payload)
|
|
174
139
|
|
|
175
140
|
message = response_data.get("message")
|
|
176
141
|
logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
|
|
177
142
|
|
|
178
143
|
return message == "ok"
|
|
179
144
|
|
|
180
|
-
def list_resources(self, site: str, ctrl_env: str) -> list[str]:
|
|
145
|
+
def list_resources(self, site: str, ctrl_env: str, vregion: str | None = None) -> list[str]:
|
|
181
146
|
"""
|
|
182
147
|
列举 CTRL 环境资源列表,连同 agent 列表
|
|
183
148
|
|
|
184
149
|
Args:
|
|
185
150
|
site: 站点名称
|
|
186
151
|
ctrl_env: 控制环境
|
|
187
|
-
|
|
152
|
+
vregion: 虚拟区域
|
|
188
153
|
Returns:
|
|
189
154
|
list[str]: CTRL 环境资源列表
|
|
190
155
|
"""
|
|
@@ -193,13 +158,44 @@ class DFlowClient:
|
|
|
193
158
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
|
|
194
159
|
|
|
195
160
|
# 构建请求数据
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
response_data =
|
|
161
|
+
headers = self.bytecloud_client.build_request_headers(site, vregion)
|
|
162
|
+
payload = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
|
|
163
|
+
response_data = make_request("POST", url, headers, payload)
|
|
199
164
|
|
|
165
|
+
# curl_cmd = output_curl(url, headers, payload)
|
|
166
|
+
# print(f"Equivalent curl:\n{curl_cmd}")
|
|
167
|
+
|
|
200
168
|
try:
|
|
201
169
|
data = response_data.get("data", {})
|
|
202
170
|
items = data.get("items", [])
|
|
203
171
|
return [item["name"] for item in items]
|
|
204
172
|
except (KeyError, AttributeError, Exception) as e:
|
|
205
173
|
raise Exception(f"无法从响应中提取 CTRL 环境资源列表数据: {str(e)}")
|
|
174
|
+
|
|
175
|
+
def get_all_ctrl_envs(self, site: str) -> dict[str, list[str]]:
|
|
176
|
+
"""获取所有可用的控制环境列表,返回以domain为键,ctrl_env列表为值的字典"""
|
|
177
|
+
site_info = self.bytecloud_client.get_site_config(site)
|
|
178
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeRegions"
|
|
179
|
+
resp_json = make_request("POST", url, self.bytecloud_client.build_request_headers(site, None), {})
|
|
180
|
+
if resp_json["code"] != 0:
|
|
181
|
+
raise Exception(resp_json["message"])
|
|
182
|
+
|
|
183
|
+
# print(f"Fetched regions info: {json.dumps(resp_json["data"], ensure_ascii=False, indent=2)}")
|
|
184
|
+
|
|
185
|
+
# 提取所有控制环境列表,格式为以domain为键,ctrl_env列表为值的字典
|
|
186
|
+
all_ctrl_envs_by_domain = collections.defaultdict(list[str])
|
|
187
|
+
|
|
188
|
+
# 从ops_platform_region中提取ctrl_env_list
|
|
189
|
+
if "bytedts_env" in resp_json["data"]:
|
|
190
|
+
for bytedts_env in resp_json["data"]["bytedts_env"]:
|
|
191
|
+
if "ctrl_env_list" in bytedts_env and "domain" in bytedts_env:
|
|
192
|
+
env = bytedts_env["env"]
|
|
193
|
+
region = bytedts_env["include_region"][0] if bytedts_env["include_region"] else "Unknown"
|
|
194
|
+
env_region_key = env + "|" + region
|
|
195
|
+
for ctrl_env_item in bytedts_env["ctrl_env_list"]:
|
|
196
|
+
if "ctrl_env" in ctrl_env_item:
|
|
197
|
+
ctrl_env = ctrl_env_item["ctrl_env"]
|
|
198
|
+
# 将ctrl_env添加到domain对应的列表中
|
|
199
|
+
all_ctrl_envs_by_domain[env_region_key].append(ctrl_env)
|
|
200
|
+
|
|
201
|
+
return dict(all_ctrl_envs_by_domain)
|
dtsdance/dsyncer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
from dtsdance.ddutil import make_request
|
|
1
2
|
from .bytecloud import ByteCloudClient
|
|
2
3
|
from typing import Any, NamedTuple, Tuple, cast, Optional
|
|
3
4
|
from loguru import logger
|
|
4
|
-
import requests
|
|
5
5
|
import re
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ class DSyncerClient:
|
|
|
21
21
|
self.envs = envs
|
|
22
22
|
self.bytecloud_client = bytecloud_client
|
|
23
23
|
|
|
24
|
-
def _build_headers(self,
|
|
24
|
+
def _build_headers(self, site: str, secret_api: bool = False) -> dict[str, str]:
|
|
25
25
|
"""
|
|
26
26
|
构建请求头
|
|
27
27
|
|
|
@@ -32,51 +32,15 @@ class DSyncerClient:
|
|
|
32
32
|
Returns:
|
|
33
33
|
dict[str, str]: 请求头字典
|
|
34
34
|
"""
|
|
35
|
-
jwt_token = self.bytecloud_client.get_jwt_token(
|
|
36
|
-
headers = {"X-Jwt-Token": jwt_token
|
|
35
|
+
jwt_token = self.bytecloud_client.get_jwt_token(site)
|
|
36
|
+
headers = {"X-Jwt-Token": jwt_token}
|
|
37
37
|
|
|
38
38
|
if secret_api:
|
|
39
|
-
token = self.envs[
|
|
39
|
+
token = self.envs[site].token
|
|
40
40
|
headers["Authorization"] = f"Token {token}"
|
|
41
41
|
|
|
42
42
|
return headers
|
|
43
43
|
|
|
44
|
-
def _make_request(self, method: str, url: str, headers: dict[str, str], data_raw: Optional[dict] = None) -> dict[str, Any]:
|
|
45
|
-
"""
|
|
46
|
-
发送 HTTP 请求的通用方法
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
method: HTTP 方法 (GET/POST)
|
|
50
|
-
url: 请求 URL
|
|
51
|
-
headers: 请求头
|
|
52
|
-
json_data: POST 请求的 JSON 数据
|
|
53
|
-
operation_name: 操作名称,用于日志记录
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
dict[str, Any]: 解析后的 JSON 响应
|
|
57
|
-
"""
|
|
58
|
-
response = None
|
|
59
|
-
try:
|
|
60
|
-
if method.upper() == "GET":
|
|
61
|
-
response = requests.get(url, headers=headers)
|
|
62
|
-
elif method.upper() == "POST":
|
|
63
|
-
response = requests.post(url, json=data_raw, headers=headers)
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
66
|
-
|
|
67
|
-
# 检查响应状态码
|
|
68
|
-
# response.raise_for_status()
|
|
69
|
-
|
|
70
|
-
# 解析 JSON 响应
|
|
71
|
-
return response.json()
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
error_msg = f"_make_request occur error, error: {e}"
|
|
75
|
-
if response is not None:
|
|
76
|
-
error_msg += f", response.text: {response.text}"
|
|
77
|
-
logger.warning(error_msg)
|
|
78
|
-
raise
|
|
79
|
-
|
|
80
44
|
def _acquire_task_info(self, site: str, task_id: str) -> dict[str, str]:
|
|
81
45
|
"""
|
|
82
46
|
获取 DSyncer 任务信息
|
|
@@ -95,7 +59,7 @@ class DSyncerClient:
|
|
|
95
59
|
# 准备请求头
|
|
96
60
|
headers = self._build_headers(site)
|
|
97
61
|
|
|
98
|
-
return
|
|
62
|
+
return make_request("GET", url, headers)
|
|
99
63
|
|
|
100
64
|
def get_dflow_task_info(self, site: str, task_id: str) -> tuple[str, str]:
|
|
101
65
|
"""
|
|
@@ -108,7 +72,7 @@ class DSyncerClient:
|
|
|
108
72
|
# 准备请求头
|
|
109
73
|
headers = self._build_headers(site)
|
|
110
74
|
|
|
111
|
-
json_data =
|
|
75
|
+
json_data = make_request("GET", url, headers)
|
|
112
76
|
|
|
113
77
|
message = json_data.get("message", "")
|
|
114
78
|
logger.debug(f"get task migrate info {site} {task_id}, message: {message}")
|
|
@@ -214,9 +178,9 @@ class DSyncerClient:
|
|
|
214
178
|
# 准备请求头
|
|
215
179
|
headers = self._build_headers(site, secret_api=True)
|
|
216
180
|
|
|
217
|
-
|
|
181
|
+
payload = {"task_id": task_id}
|
|
218
182
|
|
|
219
|
-
response_data =
|
|
183
|
+
response_data = make_request("POST", url, headers, payload)
|
|
220
184
|
|
|
221
185
|
message = response_data.get("message", "")
|
|
222
186
|
logger.debug(f"get task migrate status {site} {task_id}, message: {message}")
|
|
@@ -236,9 +200,9 @@ class DSyncerClient:
|
|
|
236
200
|
# 准备请求头
|
|
237
201
|
headers = self._build_headers(site, secret_api=True)
|
|
238
202
|
|
|
239
|
-
|
|
203
|
+
payload = {"task_id_list": [task_id]}
|
|
240
204
|
|
|
241
|
-
response_data =
|
|
205
|
+
response_data = make_request("POST", url, headers, payload)
|
|
242
206
|
|
|
243
207
|
logger.debug(f"migration_mark_rollback return {site} {task_id}, json_data: {response_data}")
|
|
244
208
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
@@ -255,9 +219,9 @@ class DSyncerClient:
|
|
|
255
219
|
# 准备请求头
|
|
256
220
|
headers = self._build_headers(site, secret_api=True)
|
|
257
221
|
|
|
258
|
-
|
|
222
|
+
payload = {"task_id": task_id}
|
|
259
223
|
|
|
260
|
-
response_data =
|
|
224
|
+
response_data = make_request("POST", url, headers, payload)
|
|
261
225
|
|
|
262
226
|
logger.debug(f"migration_mark_success return {site} {task_id}, json_data: {response_data}")
|
|
263
227
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
@@ -277,7 +241,7 @@ class DSyncerClient:
|
|
|
277
241
|
# 准备请求头
|
|
278
242
|
headers = self._build_headers(site, secret_api=True)
|
|
279
243
|
|
|
280
|
-
|
|
244
|
+
payload = {
|
|
281
245
|
"task_id": task_id,
|
|
282
246
|
"delay_threshold": "20s",
|
|
283
247
|
"dsyncer_task_pause_threshold": "180s",
|
|
@@ -289,7 +253,7 @@ class DSyncerClient:
|
|
|
289
253
|
"app_parallel": app_parallel,
|
|
290
254
|
}
|
|
291
255
|
|
|
292
|
-
response_data =
|
|
256
|
+
response_data = make_request("POST", url, headers, payload)
|
|
293
257
|
|
|
294
258
|
logger.debug(f"migrate_task return {site} {task_id}, json_data: {response_data}")
|
|
295
259
|
message = response_data.get("message")
|
dtsdance/feishu_base.py
CHANGED
|
@@ -100,13 +100,15 @@ class FeishuBase:
|
|
|
100
100
|
headers["Authorization"] = f"Bearer {token}"
|
|
101
101
|
kwargs["headers"] = headers
|
|
102
102
|
|
|
103
|
+
response: requests.Response | None = None
|
|
103
104
|
try:
|
|
104
105
|
response = requests.request(method, url, **kwargs)
|
|
105
|
-
logger.debug(f"response_json: {response.json()}")
|
|
106
|
+
# logger.debug(f"response_json: {response.json()}")
|
|
106
107
|
response.raise_for_status()
|
|
107
108
|
return response
|
|
108
109
|
except requests.RequestException as e:
|
|
109
|
-
|
|
110
|
+
error_detail = response.json() if response is not None else "无响应"
|
|
111
|
+
logger.warning(f"API请求失败: {method} {url}, error: {e}, response_json: {error_detail}")
|
|
110
112
|
raise
|
|
111
113
|
|
|
112
114
|
def upload_image(self, image_path: str) -> str | None:
|
|
@@ -265,7 +267,7 @@ class FeishuBase:
|
|
|
265
267
|
logger.warning(f"回复消息失败: {e}")
|
|
266
268
|
raise
|
|
267
269
|
|
|
268
|
-
def batch_get_user_ids(self, emails: list[str]) ->
|
|
270
|
+
def batch_get_user_ids(self, emails: list[str]) -> dict[str, None | str]:
|
|
269
271
|
"""
|
|
270
272
|
批量获取用户ID
|
|
271
273
|
|
|
@@ -273,7 +275,7 @@ class FeishuBase:
|
|
|
273
275
|
emails: 用户邮箱列表
|
|
274
276
|
|
|
275
277
|
Returns:
|
|
276
|
-
|
|
278
|
+
批量获取结果(email -> user_id 的字典映射)
|
|
277
279
|
|
|
278
280
|
Raises:
|
|
279
281
|
Exception: 获取失败
|
|
@@ -281,16 +283,51 @@ class FeishuBase:
|
|
|
281
283
|
payload = {"emails": emails, "include_resigned": False}
|
|
282
284
|
|
|
283
285
|
try:
|
|
284
|
-
response = self.make_request("POST",
|
|
286
|
+
response = self.make_request("POST", "/contact/v3/users/batch_get_id?user_id_type=user_id", json=payload)
|
|
285
287
|
result = response.json()
|
|
286
288
|
if result.get("code") != 0:
|
|
287
289
|
raise Exception(f"请求失败: {result.get('msg')}")
|
|
288
290
|
|
|
289
|
-
|
|
291
|
+
user_dict = {user.get("email"): user.get("user_id", None) for user in result.get("data", {}).get("user_list", []) if user.get("email")}
|
|
290
292
|
|
|
291
293
|
logger.debug(f"发送响应logid: {response.headers.get('X-Tt-Logid')}")
|
|
292
294
|
|
|
293
|
-
return
|
|
295
|
+
return user_dict
|
|
294
296
|
except Exception as e:
|
|
295
297
|
logger.warning(f"批量获取用户ID失败: {e}")
|
|
296
298
|
raise
|
|
299
|
+
|
|
300
|
+
def batch_get_all_user_ids(self, emails: list[str]) -> dict[str, None | str]:
|
|
301
|
+
"""
|
|
302
|
+
批量获取所有用户ID,自动分批处理(每批最多50个)
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
emails: 用户邮箱列表
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
所有用户ID字典(email -> user_id 的映射)
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
Exception: 获取失败
|
|
312
|
+
"""
|
|
313
|
+
if not emails:
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
all_user_dict = {}
|
|
317
|
+
batch_size = 50
|
|
318
|
+
|
|
319
|
+
# 分批处理
|
|
320
|
+
for i in range(0, len(emails), batch_size):
|
|
321
|
+
batch_emails = emails[i : i + batch_size]
|
|
322
|
+
logger.info(f"正在获取第 {i // batch_size + 1} 批用户ID,数量: {len(batch_emails)}")
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
batch_user_dict = self.batch_get_user_ids(batch_emails)
|
|
326
|
+
all_user_dict.update(batch_user_dict)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"获取第 {i // batch_size + 1} 批用户ID失败: {e}")
|
|
329
|
+
# 继续处理下一批,不中断整个流程
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
logger.info(f"共获取到 {len(all_user_dict)} 个用户ID")
|
|
333
|
+
return all_user_dict
|
|
@@ -40,11 +40,11 @@ class SpaceXClient:
|
|
|
40
40
|
logger.warning(f"do quest queryServerMeta exception: {e}")
|
|
41
41
|
raise
|
|
42
42
|
|
|
43
|
-
def register_resource(self, site: str,
|
|
43
|
+
def register_resource(self, site: str, payload: dict[str, Any]) -> bool:
|
|
44
44
|
site_info = self.bytecloud_client.get_site_config(site)
|
|
45
45
|
url = f"{site_info.endpoint_bytedts_spacex}/resource/v1/registerResource"
|
|
46
46
|
try:
|
|
47
|
-
response = requests.post(url, json=
|
|
47
|
+
response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
|
|
48
48
|
# print(f"response status code: {response.status_code}, response text: {response.text}")
|
|
49
49
|
|
|
50
50
|
response.raise_for_status()
|
|
@@ -56,11 +56,11 @@ class SpaceXClient:
|
|
|
56
56
|
logger.warning(f"do quest registerResource exception: {e}")
|
|
57
57
|
raise
|
|
58
58
|
|
|
59
|
-
def list_resources(self, site: str,
|
|
59
|
+
def list_resources(self, site: str, payload: dict[str, Any]) -> list[str]:
|
|
60
60
|
site_info = self.bytecloud_client.get_site_config(site)
|
|
61
61
|
url = f"{site_info.endpoint_bytedts_spacex}/resource/v1/listResource"
|
|
62
62
|
try:
|
|
63
|
-
response = requests.post(url, json=
|
|
63
|
+
response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
|
|
64
64
|
# print(f"response status code: {response.status_code}, response text: {response.text}")
|
|
65
65
|
|
|
66
66
|
response.raise_for_status()
|
|
@@ -78,7 +78,7 @@ class SpaceXClient:
|
|
|
78
78
|
def register_gateway(self, site: str, gateway_info: GatewayInfo) -> bool:
|
|
79
79
|
site_info = self.bytecloud_client.get_site_config(site)
|
|
80
80
|
url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/registryGateway"
|
|
81
|
-
|
|
81
|
+
payload = {
|
|
82
82
|
"region": gateway_info.mgr_name,
|
|
83
83
|
"server_region": gateway_info.mgr_name,
|
|
84
84
|
"cluster_region": gateway_info.ctrl_name,
|
|
@@ -97,7 +97,7 @@ class SpaceXClient:
|
|
|
97
97
|
"unified_tao_service": 1,
|
|
98
98
|
}
|
|
99
99
|
try:
|
|
100
|
-
response = requests.post(url, json=
|
|
100
|
+
response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
|
|
101
101
|
response.raise_for_status()
|
|
102
102
|
|
|
103
103
|
result = response.json()
|
|
@@ -107,11 +107,11 @@ class SpaceXClient:
|
|
|
107
107
|
logger.warning(f"do quest registryGateway exception: {e}")
|
|
108
108
|
raise
|
|
109
109
|
|
|
110
|
-
def gateway_operation(self, site: str, operation: str,
|
|
110
|
+
def gateway_operation(self, site: str, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
111
111
|
site_info = self.bytecloud_client.get_site_config(site)
|
|
112
112
|
url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/{operation}"
|
|
113
113
|
try:
|
|
114
|
-
response = requests.post(url, json=
|
|
114
|
+
response = requests.post(url, json=payload, headers=self.bytecloud_client.build_request_headers(site))
|
|
115
115
|
# print(f"response status code: {response.status_code}, response text: {response.text}")
|
|
116
116
|
|
|
117
117
|
response.raise_for_status()
|
dtsdance/tcc_inner.py
CHANGED
|
@@ -20,14 +20,14 @@ class TCCInnerClient:
|
|
|
20
20
|
region: str,
|
|
21
21
|
dir: str,
|
|
22
22
|
conf_name: str,
|
|
23
|
-
) -> dict[str, Any]:
|
|
23
|
+
) -> list[dict[str, Any]]:
|
|
24
24
|
"""
|
|
25
25
|
List TCC configurations.
|
|
26
26
|
"""
|
|
27
27
|
site_info = self.bytecloud_client.get_site_config(site)
|
|
28
28
|
url = f"{site_info.endpoint}/api/v3/tcc/bcc/config/list_v2"
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
payload = {
|
|
31
31
|
"ns_name": ns_name,
|
|
32
32
|
"region": region,
|
|
33
33
|
"dir_path": dir,
|
|
@@ -40,7 +40,7 @@ class TCCInnerClient:
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
try:
|
|
43
|
-
response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=
|
|
43
|
+
response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=payload, timeout=30)
|
|
44
44
|
response.raise_for_status()
|
|
45
45
|
result = response.json()
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ class TCCInnerClient:
|
|
|
49
49
|
if base_resp.get("error_code", 0) != 0:
|
|
50
50
|
raise TCCError(f"TCC API error: {base_resp.get('error_message', 'Unknown error')}")
|
|
51
51
|
|
|
52
|
-
return result.get("data",
|
|
52
|
+
return result.get("data", [])
|
|
53
53
|
|
|
54
54
|
except requests.RequestException as e:
|
|
55
55
|
raise TCCError(f"Failed to get TCC config: {str(e)}") from e
|
dtsdance/tcc_open.py
CHANGED
dts_dance-0.2.4.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
|
|
2
|
-
dtsdance/bytecloud.py,sha256=s1suo-YexMH6gUHYwC6P5k6AeyOm7Q_acYh_gHCDVjI,5654
|
|
3
|
-
dtsdance/dflow.py,sha256=ggwNB2x-LgnpI9nzNTTnYnGfYyGH5rYcHHBhC4pOlfg,7059
|
|
4
|
-
dtsdance/dsyncer.py,sha256=3Oj1Ko5FuB_sUm9Hj-hBy95wee3b6TH2FXKoPFGsakM,11193
|
|
5
|
-
dtsdance/feishu_base.py,sha256=Dsl8CZZ3wyZf3TEiiUAs3_WpX1pinCuWFwqpm29ROVc,10414
|
|
6
|
-
dtsdance/feishu_table.py,sha256=JleCjQJ-rcbC15v2UAc5BK-YioB2SMGsdGztQ9BaT4k,10779
|
|
7
|
-
dtsdance/metrics_fe.py,sha256=hzIl5BJmuCrkqJOHELVzXm3YAqrPttbyVkKBglS4mgQ,18978
|
|
8
|
-
dtsdance/s3.py,sha256=Bh-cwLksfO5PewNtIzE_Md3rRLDLI1DUVoOD7Pou5T8,1294
|
|
9
|
-
dtsdance/spacex_bytedts.py,sha256=oVnHXTj-cuq2vcsUJq33_aaq0TQp7A52kldvspKueI4,4799
|
|
10
|
-
dtsdance/tcc_inner.py,sha256=6Tuq_r2nNCK_HC4hqEGh7UVAa8kDtuIcU95WBu4zuFQ,1623
|
|
11
|
-
dtsdance/tcc_open.py,sha256=Qb_ue3xH0CSsoReItdFKuvW7JsDTfFkDNIB_4zjzSQI,6893
|
|
12
|
-
dts_dance-0.2.4.dist-info/METADATA,sha256=LY_7WWh06QNmhr_1vyuMTtKDrx88N0P1BjGEURYAl6U,833
|
|
13
|
-
dts_dance-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
-
dts_dance-0.2.4.dist-info/RECORD,,
|
|
File without changes
|