dts-dance 0.1.8__tar.gz → 0.2.0__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.
Files changed (27) hide show
  1. {dts_dance-0.1.8 → dts_dance-0.2.0}/.gitignore +1 -3
  2. {dts_dance-0.1.8 → dts_dance-0.2.0}/PKG-INFO +5 -4
  3. dts_dance-0.2.0/config.yaml +36 -0
  4. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/bytecloud.py +40 -34
  5. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/dflow.py +25 -48
  6. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/dsyncer.py +48 -48
  7. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/metrics_fe.py +13 -13
  8. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/s3.py +1 -2
  9. dts_dance-0.2.0/dtsdance/spacex.py +68 -0
  10. dts_dance-0.2.0/dtsdance/tcc_inner.py +55 -0
  11. dts_dance-0.1.8/dtsdance/tcc.py → dts_dance-0.2.0/dtsdance/tcc_open.py +15 -12
  12. {dts_dance-0.1.8 → dts_dance-0.2.0}/pyproject.toml +6 -5
  13. {dts_dance-0.1.8 → dts_dance-0.2.0}/tests/config.py +10 -2
  14. dts_dance-0.2.0/tests/test_dflow.py +14 -0
  15. dts_dance-0.2.0/tests/test_spacex.py +30 -0
  16. dts_dance-0.2.0/tests/test_tcc.py +22 -0
  17. {dts_dance-0.1.8 → dts_dance-0.2.0}/uv.lock +76 -4
  18. dts_dance-0.1.8/config.example.yaml +0 -5
  19. dts_dance-0.1.8/dtsdance/spacex.py +0 -69
  20. dts_dance-0.1.8/tests/test_dflow.py +0 -21
  21. {dts_dance-0.1.8 → dts_dance-0.2.0}/.python-version +0 -0
  22. {dts_dance-0.1.8 → dts_dance-0.2.0}/CLAUDE.md +0 -0
  23. {dts_dance-0.1.8 → dts_dance-0.2.0}/DEV.md +0 -0
  24. {dts_dance-0.1.8 → dts_dance-0.2.0}/README.md +0 -0
  25. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/__init__.py +0 -0
  26. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/feishu_base.py +0 -0
  27. {dts_dance-0.1.8 → dts_dance-0.2.0}/dtsdance/feishu_table.py +0 -0
@@ -28,6 +28,4 @@ env/
28
28
  # 临时文件
29
29
  .DS_Store
30
30
  .env
31
- .env.local
32
-
33
- config.yaml
31
+ .env.local
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dts-dance
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: dts dance lib
5
5
  Keywords: observation,tools
6
6
  Requires-Python: >=3.12
7
- Requires-Dist: loguru==0.7.3
8
- Requires-Dist: pyyaml==6.0.3
9
- Requires-Dist: requests==2.32.5
7
+ Requires-Dist: boto3<2.0.0,>=1.42.27
8
+ Requires-Dist: loguru<0.8.0,>=0.7.3
9
+ Requires-Dist: pyyaml<7.0.0,>=6.0.3
10
+ Requires-Dist: requests<3.0.0,>=2.32.5
10
11
  Description-Content-Type: text/markdown
11
12
 
12
13
  # dts-dance
@@ -0,0 +1,36 @@
1
+ sites:
2
+ boe:
3
+ endpoint: https://cloud-boe.bytedance.net
4
+ endpoint_bytedts_spacex: https://bytedts-spacex-boe.bytedance.net
5
+ svc_account: bytedts
6
+ svc_secret: a47ee5fe1783b810a79894a3eb49fb3e
7
+ boe-i18n:
8
+ endpoint: https://cloud-boei18n.bytedance.net
9
+ endpoint_bytedts_spacex: https://bytedts-spacex-boei18n.byteintl.net
10
+ svc_account: bytedts_spacex_i18n
11
+ svc_secret: 7f83582813835e8e9806f63eb15adbe1
12
+ cn:
13
+ endpoint: https://cloud.bytedance.net
14
+ endpoint_bytedts_spacex: https://bytedts-spacex-cn.bytedance.net
15
+ svc_account: bytedts
16
+ svc_secret: a47ee5fe1783b810a79894a3eb49fb3e
17
+ i18n-bd:
18
+ endpoint: https://cloud.byteintl.net
19
+ endpoint_bytedts_spacex: https://bytedts-spacex-i18ndb.byted.org
20
+ svc_account: bytedts_spacex_i18n
21
+ svc_secret: 7f83582813835e8e9806f63eb15adbe1
22
+ i18n-tt:
23
+ endpoint: https://cloud.tiktok-row.net
24
+ endpoint_bytedts_spacex: https://bytedts-spacex-i18n.tiktok-row.org
25
+ svc_account: bytedts_spacex_i18n
26
+ svc_secret: 7f83582813835e8e9806f63eb15adbe1
27
+ us-ttp:
28
+ endpoint: https://cloud.tiktok-us.net # 区别于官网介绍,使用这个当前能通
29
+ endpoint_bytedts_spacex: https://bytedts-spacex-usttp-api.tiktok-us.org
30
+ svc_account: bytedts_spacex
31
+ svc_secret: 70a99c2a17599c0335527e82b3d1381c
32
+ # eu-ttp: # 还没建设好,先不用试了
33
+ # endpoint: https://cloud-eu.tiktok-row.net
34
+ # endpoint_bytedts_spacex: https://bytedts-spacex-eu.tiktok-eu.org
35
+ # svc_account: bytedts_spacex
36
+ # svc_secret: 70a99c2a17599c0335527e82b3d1381c
@@ -6,27 +6,29 @@ from loguru import logger
6
6
  from typing import NamedTuple
7
7
 
8
8
 
9
- class ByteCloudEnvInfo(NamedTuple):
9
+ class SiteConfig(NamedTuple):
10
10
  name: str
11
11
  endpoint: str
12
- token: str
12
+ svc_account: str
13
+ svc_secret: str
14
+ endpoint_bytedts_spacex: str | None = None
13
15
 
14
16
 
15
- class ByteCloudHelper:
17
+ class ByteCloudClient:
16
18
  """
17
- ByteCloud Helper
19
+ ByteCloud Client
18
20
  """
19
21
 
20
22
  # 每小时刷新一次,单位为秒
21
23
  _REFRESH_INTERVAL = 1 * 60 * 60
22
24
 
23
- def __init__(self, envs: dict[str, ByteCloudEnvInfo]):
25
+ def __init__(self, sites: dict[str, SiteConfig]):
24
26
  """
25
- 初始化 ByteCloud Helper
27
+ 初始化 ByteCloud Client
26
28
  从配置文件加载所有环境的信息,并为每个环境初始化 JWT 令牌
27
- envs 中保存内容 list[(name, endpoint, token)]
29
+ sites 中保存内容 list[(name, endpoint, svc_account, svc_secret)]
28
30
  """
29
- self.envs = envs
31
+ self.sites = sites
30
32
 
31
33
  # 初始化线程锁,用于保护 jwt_tokens 的并发访问
32
34
  self.token_lock = threading.Lock()
@@ -66,20 +68,20 @@ class ByteCloudHelper:
66
68
  """
67
69
  logger.debug("开始刷新所有环境的 JWT 令牌...")
68
70
 
69
- for _, env in self.envs.items():
71
+ for _, site in self.sites.items():
70
72
  try:
71
73
  # 刷新令牌
72
- new_token = self._acquire_jwt_token(env.endpoint, env.token)
74
+ new_token = self._acquire_jwt_token(site.endpoint, site.svc_secret)
73
75
  # 使用线程锁更新缓存中的 JWT 令牌
74
76
  with self.token_lock:
75
- self.jwt_tokens[env.name] = new_token
76
- logger.debug(f"环境 {env.name} 的 JWT 令牌成功刷新,新令牌: {new_token}")
77
+ self.jwt_tokens[site.name] = new_token
78
+ logger.debug(f"环境 {site.name} 的 JWT 令牌成功刷新,新令牌: {new_token}")
77
79
  except Exception as e:
78
- logger.error(f"环境 {env.name} 的 JWT 令牌刷新失败: {e}")
80
+ logger.error(f"环境 {site.name} 的 JWT 令牌刷新失败: {e}")
79
81
 
80
82
  logger.debug(f"所有环境的 JWT 令牌已成功刷新。jwt_tokens: {self.jwt_tokens}")
81
83
 
82
- def _acquire_jwt_token(self, endpoint: str, token: str) -> str:
84
+ def _acquire_jwt_token(self, endpoint: str, svc_secret: str) -> str:
83
85
  """
84
86
  获取 JWT 令牌
85
87
 
@@ -93,7 +95,7 @@ class ByteCloudHelper:
93
95
  url = endpoint + "/auth/api/v1/jwt"
94
96
  headers = {
95
97
  "Content-Type": "application/json",
96
- "Authorization": "Bearer " + token,
98
+ "Authorization": "Bearer " + svc_secret,
97
99
  }
98
100
 
99
101
  try:
@@ -125,39 +127,43 @@ class ByteCloudHelper:
125
127
  logger.error(f"获取JWT令牌时出错: {e}")
126
128
  raise
127
129
 
128
- def get_jwt_token(self, env: str) -> str:
130
+ def get_jwt_token(self, site: str) -> str:
129
131
  """
130
- 获取指定环境的 JWT 令牌
132
+ 获取指定站点的 JWT 令牌
133
+ """
134
+ # 使用线程锁保护并发访问
135
+ with self.token_lock:
136
+ if site not in self.jwt_tokens:
137
+ raise KeyError(f"站点 {site} 的 JWT 令牌不存在")
138
+ return self.jwt_tokens[site]
139
+
140
+ def build_request_headers(self, site: str) -> dict[str, str]:
141
+ """
142
+ 构建请求头
131
143
 
132
144
  Args:
133
- env: 环境名称,如 'China-North', 'China-East', 'Singapore-Central' 等
145
+ site: 站点名称
134
146
 
135
147
  Returns:
136
- str: 当前有效的 JWT 令牌
137
-
138
- Raises:
139
- KeyError: 如果指定的环境不存在
148
+ dict[str, str]: 请求头字典
140
149
  """
141
- # 使用线程锁保护并发访问
142
- with self.token_lock:
143
- if env not in self.jwt_tokens:
144
- raise KeyError(f"环境 {env} 的 JWT 令牌不存在")
145
- return self.jwt_tokens[env]
150
+ jwt_token = self.get_jwt_token(site)
151
+ return {"Content-Type": "application/json", "x-jwt-token": jwt_token}
146
152
 
147
- def get_env_info(self, env: str) -> ByteCloudEnvInfo:
153
+ def get_site_info(self, site: str) -> SiteConfig:
148
154
  """
149
155
  获取指定环境的信息
150
156
 
151
157
  Args:
152
- env: 环境名称
158
+ site: 站点名称
153
159
 
154
160
  Returns:
155
- ByteCloudEnvInfo: 指定环境的信息
161
+ SiteConfig: 指定站点的信息
156
162
 
157
163
  Raises:
158
- KeyError: 如果指定的环境不存在
164
+ KeyError: 如果指定的站点不存在
159
165
  """
160
- if env not in self.envs:
161
- raise KeyError(f"环境 {env} 不存在")
166
+ if site not in self.sites:
167
+ raise KeyError(f"站点 {site} 不存在")
162
168
 
163
- return self.envs[env]
169
+ return self.sites[site]
@@ -1,27 +1,13 @@
1
1
  from typing import Any, cast, Optional
2
2
  from loguru import logger
3
- from .bytecloud import ByteCloudHelper
3
+ from .bytecloud import ByteCloudClient
4
4
  import requests
5
5
 
6
6
 
7
- class DFlowHelper:
7
+ class DFlowClient:
8
8
 
9
- def __init__(self, bytecloud_helper: ByteCloudHelper) -> None:
10
- self.bytecloud_helper = bytecloud_helper
11
-
12
- def _build_headers(self, env: str) -> dict[str, str]:
13
- """
14
- 构建请求头
15
-
16
- Args:
17
- env: 环境名称
18
-
19
- Returns:
20
- dict[str, str]: 请求头字典
21
- """
22
- jwt_token = self.bytecloud_helper.get_jwt_token(env)
23
- headers = {"x-jwt-token": jwt_token}
24
- return headers
9
+ def __init__(self, bytecloud_client: ByteCloudClient) -> None:
10
+ self.bytecloud_client = bytecloud_client
25
11
 
26
12
  def _make_request(self, method: str, url: str, headers: dict[str, str], json_data: Optional[dict] = None) -> dict[str, Any]:
27
13
  """
@@ -58,30 +44,27 @@ class DFlowHelper:
58
44
  logger.warning(error_msg)
59
45
  raise
60
46
 
61
- def get_dflow_info(self, env: str, task_id: str) -> dict[str, Any]:
47
+ def get_dflow_info(self, site: str, task_id: str) -> dict[str, Any]:
62
48
  """
63
49
  获取 DFlow 任务信息
64
50
 
65
51
  Args:
66
- env: 环境名称
52
+ site: 站点名称
67
53
  task_id: DFlow 任务 ID
68
54
 
69
55
  Returns:
70
56
  dict[str, Any]: DFlow 任务信息,包含 create_time 等字段
71
57
  """
72
58
  # 构建 API URL
73
- env_info = self.bytecloud_helper.get_env_info(env)
74
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
75
-
76
- # 准备请求头
77
- headers = self._build_headers(env)
59
+ site_info = self.bytecloud_client.get_site_info(site)
60
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
78
61
 
79
62
  # 构建请求数据
80
63
  json_data = {"id": int(task_id)}
81
64
 
82
- response_data = self._make_request("POST", url, headers, json_data)
65
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
83
66
 
84
- logger.info(f"get_dflow_info {env} {task_id}, message: {response_data.get('message')}")
67
+ logger.info(f"get_dflow_info {site} {task_id}, message: {response_data.get('message')}")
85
68
 
86
69
  try:
87
70
  data = cast(dict, response_data.get("data", {}))
@@ -99,71 +82,65 @@ class DFlowHelper:
99
82
  except (KeyError, AttributeError, Exception) as e:
100
83
  raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
101
84
 
102
- def generate_task_url(self, env: str, task_id: str) -> str:
85
+ def generate_task_url(self, site: str, task_id: str) -> str:
103
86
  """
104
87
  获取 DFlow 任务详情页面的 URL
105
88
 
106
89
  Args:
107
- env: 环境名称
90
+ site: 站点名称
108
91
  task_id: DFlow 任务 ID
109
92
 
110
93
  Returns:
111
94
  str: DFlow 任务详情页面的 URL
112
95
  """
113
96
  # 根据环境生成对应的 scope 参数
114
- env_info = self.bytecloud_helper.get_env_info(env)
115
- return f"{env_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={env}"
97
+ site_info = self.bytecloud_client.get_site_info(site)
98
+ return f"{site_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={site}"
116
99
 
117
- def init_resources(self, env: str, ctrl_env: str) -> bool:
100
+ def init_resources(self, site: str, ctrl_env: str) -> bool:
118
101
  """
119
102
  初始化 CTRL 环境资源
120
103
 
121
104
  Args:
122
- env: 环境名称
105
+ site: 站点名称
123
106
  ctrl_env: 控制环境
124
107
 
125
108
  Returns:
126
109
  bool: CTRL 环境资源初始化结果
127
110
  """
128
111
  # 构建 API URL
129
- env_info = self.bytecloud_helper.get_env_info(env)
130
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
131
-
132
- # 准备请求头
133
- headers = self._build_headers(env)
112
+ site_info = self.bytecloud_client.get_site_info(site)
113
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
134
114
 
135
115
  # 构建请求数据
136
116
  json_data = {"ctrl_env": ctrl_env}
137
117
 
138
- response_data = self._make_request("POST", url, headers, json_data)
118
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
139
119
 
140
120
  message = response_data.get("message")
141
- logger.info(f"int_resources {env} {ctrl_env}, message: {message}")
121
+ logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
142
122
 
143
123
  return message == "ok"
144
124
 
145
- def list_resources(self, env: str, ctrl_env: str) -> list[str]:
125
+ def list_resources(self, site: str, ctrl_env: str) -> list[str]:
146
126
  """
147
127
  列举 CTRL 环境资源列表
148
128
 
149
129
  Args:
150
- env: 环境名称
130
+ site: 站点名称
151
131
  ctrl_env: 控制环境
152
132
 
153
133
  Returns:
154
134
  list[str]: CTRL 环境资源列表
155
135
  """
156
136
  # 构建 API URL
157
- env_info = self.bytecloud_helper.get_env_info(env)
158
- url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
159
-
160
- # 准备请求头
161
- headers = self._build_headers(env)
137
+ site_info = self.bytecloud_client.get_site_info(site)
138
+ url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
162
139
 
163
140
  # 构建请求数据
164
141
  json_data = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
165
142
 
166
- response_data = self._make_request("POST", url, headers, json_data)
143
+ response_data = self._make_request("POST", url, self.bytecloud_client.build_request_headers(site), json_data)
167
144
 
168
145
  try:
169
146
  data = cast(dict, response_data.get("data", {}))
@@ -1,4 +1,4 @@
1
- from .bytecloud import ByteCloudHelper
1
+ from .bytecloud import ByteCloudClient
2
2
  from typing import Any, NamedTuple, Tuple, cast, Optional
3
3
  from loguru import logger
4
4
  import requests
@@ -15,11 +15,11 @@ class DSyncerEnvInfo(NamedTuple):
15
15
  token: str
16
16
 
17
17
 
18
- class DSyncerHelper:
18
+ class DSyncerClient:
19
19
 
20
- def __init__(self, envs: dict[str, DSyncerEnvInfo], bytecloud_helper: ByteCloudHelper) -> None:
20
+ def __init__(self, envs: dict[str, DSyncerEnvInfo], bytecloud_client: ByteCloudClient) -> None:
21
21
  self.envs = envs
22
- self.bytecloud_helper = bytecloud_helper
22
+ self.bytecloud_client = bytecloud_client
23
23
 
24
24
  def _build_headers(self, env: str, secret_api: bool = False) -> dict[str, str]:
25
25
  """
@@ -32,7 +32,7 @@ class DSyncerHelper:
32
32
  Returns:
33
33
  dict[str, str]: 请求头字典
34
34
  """
35
- jwt_token = self.bytecloud_helper.get_jwt_token(env)
35
+ jwt_token = self.bytecloud_client.get_jwt_token(env)
36
36
  headers = {"X-Jwt-Token": jwt_token, "x-bcgw-vregion": env}
37
37
 
38
38
  if secret_api:
@@ -41,7 +41,7 @@ class DSyncerHelper:
41
41
 
42
42
  return headers
43
43
 
44
- def _make_request(self, method: str, url: str, headers: dict[str, str], json_data: Optional[dict] = None) -> dict[str, Any]:
44
+ def _make_request(self, method: str, url: str, headers: dict[str, str], data_raw: Optional[dict] = None) -> dict[str, Any]:
45
45
  """
46
46
  发送 HTTP 请求的通用方法
47
47
 
@@ -60,7 +60,7 @@ class DSyncerHelper:
60
60
  if method.upper() == "GET":
61
61
  response = requests.get(url, headers=headers)
62
62
  elif method.upper() == "POST":
63
- response = requests.post(url, json=json_data, headers=headers)
63
+ response = requests.post(url, json=data_raw, headers=headers)
64
64
  else:
65
65
  raise ValueError(f"不支持的 HTTP 方法: {method}")
66
66
 
@@ -77,41 +77,41 @@ class DSyncerHelper:
77
77
  logger.warning(error_msg)
78
78
  raise
79
79
 
80
- def _acquire_task_info(self, env: str, task_id: str) -> dict[str, str]:
80
+ def _acquire_task_info(self, site: str, task_id: str) -> dict[str, str]:
81
81
  """
82
82
  获取 DSyncer 任务信息
83
83
 
84
84
  Args:
85
- env: 环境名称
85
+ site: 站点名称
86
86
  task_id: DSyncer 任务 ID
87
87
 
88
88
  Returns:
89
89
  dict[str, str]: DSyncer 任务的 rocket_mq_connection 信息,只包含 cluster、topic 和 group 字段
90
90
  """
91
91
  # 构建 API URL
92
- env_info = self.bytecloud_helper.get_env_info(env)
93
- url = f"{env_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/"
92
+ site_info = self.bytecloud_client.get_site_info(site)
93
+ url = f"{site_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/"
94
94
 
95
95
  # 准备请求头
96
- headers = self._build_headers(env)
96
+ headers = self._build_headers(site)
97
97
 
98
98
  return self._make_request("GET", url, headers)
99
99
 
100
- def get_dflow_task_info(self, env: str, task_id: str) -> tuple[str, str]:
100
+ def get_dflow_task_info(self, site: str, task_id: str) -> tuple[str, str]:
101
101
  """
102
102
  获取迁移后的 DFlow 任务信息
103
103
  """
104
104
  # 构建 API URL
105
- env_info = self.bytecloud_helper.get_env_info(env)
106
- url = f"{env_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/migrate/"
105
+ site_info = self.bytecloud_client.get_site_info(site)
106
+ url = f"{site_info.endpoint}/api/v1/dsyncer/openapi/taskinfo/{task_id}/migrate/"
107
107
 
108
108
  # 准备请求头
109
- headers = self._build_headers(env)
109
+ headers = self._build_headers(site)
110
110
 
111
111
  json_data = self._make_request("GET", url, headers)
112
112
 
113
113
  message = json_data.get("message", "")
114
- logger.debug(f"get task migrate info {env} {task_id}, message: {message}")
114
+ logger.debug(f"get task migrate info {site} {task_id}, message: {message}")
115
115
 
116
116
  # 从消息中提取 DFlow 任务 URL 和 ID
117
117
  # 消息格式: "任务已迁移至ByteDTS平台,请前往[https://cloud.bytedance.net/bytedts/datasync/detail/93127366537986?scope=China-North&tabKey=DetailInfo]查看详情"
@@ -133,7 +133,7 @@ class DSyncerHelper:
133
133
  logger.warning(f"could not extract dflow task info from message: {message}")
134
134
  return ("", "")
135
135
 
136
- def generate_task_url(self, env: str, task_id: str) -> str:
136
+ def generate_task_url(self, site: str, task_id: str) -> str:
137
137
  """
138
138
  获取 DSyncer 任务详情页面的 URL
139
139
 
@@ -144,8 +144,8 @@ class DSyncerHelper:
144
144
  Returns:
145
145
  str: DSyncer 任务详情页面的 URL
146
146
  """
147
- env_info = self.bytecloud_helper.get_env_info(env)
148
- return DSyncer_Task_Detail_URL.format(endpoint=env_info.endpoint, task_id=task_id)
147
+ site_info = self.bytecloud_client.get_site_info(site)
148
+ return DSyncer_Task_Detail_URL.format(endpoint=site_info.endpoint, task_id=task_id)
149
149
 
150
150
  def generate_task_grafana_url(self, task_id: str, change_time: str) -> str:
151
151
  """
@@ -200,7 +200,7 @@ class DSyncerHelper:
200
200
  except (KeyError, AttributeError, Exception) as e:
201
201
  raise Exception(f"无法从响应中提取 DSyncer 任务状态数据: {str(e)}")
202
202
 
203
- def is_task_migrate_running(self, env: str, task_id: str) -> Tuple[bool, str]:
203
+ def is_task_migrate_running(self, site: str, task_id: str) -> Tuple[bool, str]:
204
204
  """
205
205
  检查 DSyncer 任务是否正在迁移中
206
206
 
@@ -208,62 +208,62 @@ class DSyncerHelper:
208
208
  bool: 如果任务正在迁移中,返回 True;否则返回 False
209
209
  """
210
210
  # 构建 API URL
211
- env_info = self.bytecloud_helper.get_env_info(env)
212
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate/check"
211
+ site_info = self.bytecloud_client.get_site_info(site)
212
+ url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate/check"
213
213
 
214
214
  # 准备请求头
215
- headers = self._build_headers(env, secret_api=True)
215
+ headers = self._build_headers(site, secret_api=True)
216
216
 
217
- json_data = {"task_id": task_id}
217
+ data_raw = {"task_id": task_id}
218
218
 
219
- response_data = self._make_request("POST", url, headers, json_data)
219
+ response_data = self._make_request("POST", url, headers, data_raw)
220
220
 
221
221
  message = response_data.get("message", "")
222
- logger.debug(f"get task migrate status {env} {task_id}, message: {message}")
222
+ logger.debug(f"get task migrate status {site} {task_id}, message: {message}")
223
223
  if "task migrate is running" in message:
224
224
  return True, ""
225
225
  else:
226
226
  return False, response_data.get("data", {}).get("msg", {})
227
227
 
228
- def migration_rollback(self, env: str, task_id: str) -> bool:
228
+ def migration_rollback(self, site: str, task_id: str) -> bool:
229
229
  """
230
230
  执行回滚
231
231
  """
232
232
  # 构建 API URL
233
- env_info = self.bytecloud_helper.get_env_info(env)
234
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/rollback_migrate2dsyncer/"
233
+ site_info = self.bytecloud_client.get_site_info(site)
234
+ url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/rollback_migrate2dsyncer/"
235
235
 
236
236
  # 准备请求头
237
- headers = self._build_headers(env, secret_api=True)
237
+ headers = self._build_headers(site, secret_api=True)
238
238
 
239
- json_data = {"task_id_list": [task_id]}
239
+ data_raw = {"task_id_list": [task_id]}
240
240
 
241
- response_data = self._make_request("POST", url, headers, json_data)
241
+ response_data = self._make_request("POST", url, headers, data_raw)
242
242
 
243
- logger.debug(f"migration_mark_rollback return {env} {task_id}, json_data: {response_data}")
243
+ logger.debug(f"migration_mark_rollback return {site} {task_id}, json_data: {response_data}")
244
244
  success_task = response_data.get("data", {}).get("success_task", [])
245
245
  return task_id in success_task
246
246
 
247
- def migration_mark_success(self, env: str, task_id: str) -> bool:
247
+ def migration_mark_success(self, site: str, task_id: str) -> bool:
248
248
  """
249
249
  标记迁移成功
250
250
  """
251
251
  # 构建 API URL
252
- env_info = self.bytecloud_helper.get_env_info(env)
253
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/mark_migrate_success/"
252
+ site_info = self.bytecloud_client.get_site_info(site)
253
+ url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/mark_migrate_success/"
254
254
 
255
255
  # 准备请求头
256
- headers = self._build_headers(env, secret_api=True)
256
+ headers = self._build_headers(site, secret_api=True)
257
257
 
258
- json_data = {"task_id": task_id}
258
+ data_raw = {"task_id": task_id}
259
259
 
260
- response_data = self._make_request("POST", url, headers, json_data)
260
+ response_data = self._make_request("POST", url, headers, data_raw)
261
261
 
262
- logger.debug(f"migration_mark_success return {env} {task_id}, json_data: {response_data}")
262
+ logger.debug(f"migration_mark_success return {site} {task_id}, json_data: {response_data}")
263
263
  success_task = response_data.get("data", {}).get("success_task", [])
264
264
  return task_id in success_task
265
265
 
266
- def migrate_task(self, env: str, task_id: str, app_parallel: int) -> Optional[str]:
266
+ def migrate_task(self, site: str, task_id: str, app_parallel: int) -> Optional[str]:
267
267
  """
268
268
  迁移任务到DFlow
269
269
 
@@ -271,13 +271,13 @@ class DSyncerHelper:
271
271
  Optional[str]: 错误信息,成功时返回None,失败时返回错误信息
272
272
  """
273
273
  # 构建 API URL
274
- env_info = self.bytecloud_helper.get_env_info(env)
275
- url = f"{env_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate2dflow/single"
274
+ site_info = self.bytecloud_client.get_site_info(site)
275
+ url = f"{site_info.endpoint}/api/v1/dsyncer/secret_api/task/migrate2dflow/single"
276
276
 
277
277
  # 准备请求头
278
- headers = self._build_headers(env, secret_api=True)
278
+ headers = self._build_headers(site, secret_api=True)
279
279
 
280
- json_data = {
280
+ data_raw = {
281
281
  "task_id": task_id,
282
282
  "delay_threshold": "20s",
283
283
  "dsyncer_task_pause_threshold": "180s",
@@ -289,9 +289,9 @@ class DSyncerHelper:
289
289
  "app_parallel": app_parallel,
290
290
  }
291
291
 
292
- response_data = self._make_request("POST", url, headers, json_data)
292
+ response_data = self._make_request("POST", url, headers, data_raw)
293
293
 
294
- logger.debug(f"migrate_task return {env} {task_id}, json_data: {response_data}")
294
+ logger.debug(f"migrate_task return {site} {task_id}, json_data: {response_data}")
295
295
  message = response_data.get("message")
296
296
  if response_data.get("code") == 0 and message == "ok":
297
297
  err_message = None
@@ -2,7 +2,7 @@ from loguru import logger
2
2
  import requests
3
3
  import threading
4
4
  import time
5
- from typing import Dict, List, NamedTuple, Optional
5
+ from typing import NamedTuple, Optional
6
6
  from datetime import datetime, timedelta
7
7
 
8
8
 
@@ -60,9 +60,9 @@ def get_metric_type_by_name(metric_name: str) -> Optional[MetricType]:
60
60
  return None
61
61
 
62
62
 
63
- class MetricsHelper:
63
+ class MetricsClient:
64
64
  """
65
- Metrics Helper
65
+ Metrics Client
66
66
  """
67
67
 
68
68
  # https://cloud.bytedance.net/docs/metrics/docs/63bbbb1ec6b537022a6000c7/63bbd4e8777a300220d4ac3a?x-resource-account=public&x-bc-region-id=bytedance
@@ -71,7 +71,7 @@ class MetricsHelper:
71
71
 
72
72
  def __init__(self, envs: dict[str, MetricEnvInfo]):
73
73
  """
74
- 初始化 Metrics Helper
74
+ 初始化 Metrics Client
75
75
  从配置文件加载所有环境的信息,并为每个环境初始化访问令牌
76
76
  """
77
77
  self.envs = envs
@@ -80,7 +80,7 @@ class MetricsHelper:
80
80
  self.token_lock = threading.Lock()
81
81
 
82
82
  # 初始化访问令牌缓存,按环境名称索引
83
- self.tokens: Dict[str, str] = {}
83
+ self.tokens: dict[str, str] = {}
84
84
 
85
85
  # 更新所有环境的配置信息
86
86
  self._refresh_tokens()
@@ -212,8 +212,8 @@ class MetricsHelper:
212
212
  return self.envs[env_name].endpoint
213
213
 
214
214
  def _build_query_payload(
215
- self, metric_name: str, aggregator: str, start_time_ms: int, end_time_ms: int, filters: List[Dict], rate: bool, **kwargs
216
- ) -> Dict:
215
+ self, metric_name: str, aggregator: str, start_time_ms: int, end_time_ms: int, filters: list[dict], rate: bool, **kwargs
216
+ ) -> dict:
217
217
  """
218
218
  构建查询请求体
219
219
 
@@ -254,8 +254,8 @@ class MetricsHelper:
254
254
  }
255
255
 
256
256
  def _execute_metrics_query(
257
- self, env: str, metric_name: str, aggregator: str, start_time: str, end_time: str, filters: List[Dict], rate: bool, **kwargs
258
- ) -> Dict:
257
+ self, env: str, metric_name: str, aggregator: str, start_time: str, end_time: str, filters: list[dict], rate: bool, **kwargs
258
+ ) -> dict:
259
259
  """
260
260
  执行通用的指标查询
261
261
 
@@ -318,7 +318,7 @@ class MetricsHelper:
318
318
 
319
319
  def get_mq_group_metrics(
320
320
  self, env: str, cluster: str, topic: str, group: str, start_time: str, end_time: str, metric_type: MetricType, **kwargs
321
- ) -> Dict:
321
+ ) -> dict:
322
322
  """
323
323
  获取指定时间段的堆积指标
324
324
 
@@ -398,7 +398,7 @@ class MetricsHelper:
398
398
 
399
399
  return int(max_consume_tps)
400
400
 
401
- def get_dsyncer_task_metrics(self, env: str, metric_type: MetricType, dsyncer_increment_task_id: str, start_time: str, end_time: str) -> Dict:
401
+ def get_dsyncer_task_metrics(self, env: str, metric_type: MetricType, dsyncer_increment_task_id: str, start_time: str, end_time: str) -> dict:
402
402
  """
403
403
  获取 DSyncer 任务指定时间段的指标
404
404
 
@@ -434,7 +434,7 @@ class MetricsHelper:
434
434
  logger.error(f"get_task_latency_metrics env: {env}, dsyncer_increment_task_id: {dsyncer_increment_task_id}, error: {e}")
435
435
  raise Exception(f"获取指标失败: {e}")
436
436
 
437
- def get_dflow_task_metrics(self, env: str, dflow_task_id: str, metric_type: MetricType, start_time: str, end_time: str, **kwargs) -> Dict:
437
+ def get_dflow_task_metrics(self, env: str, dflow_task_id: str, metric_type: MetricType, start_time: str, end_time: str, **kwargs) -> dict:
438
438
  """
439
439
  获取指定时间段的 DFlow 指标
440
440
 
@@ -471,7 +471,7 @@ class MetricsHelper:
471
471
  raise Exception(f"获取指标失败: {e}")
472
472
 
473
473
  @staticmethod
474
- def _remove_last_datapoint(end_time: str, dps: Dict[str, float]) -> Dict[str, float]:
474
+ def _remove_last_datapoint(end_time: str, dps: dict[str, float]) -> dict[str, float]:
475
475
  """
476
476
  移除监控数据中最后一个数据点,因为最后一个点往往不准确
477
477
  只有当最后一个点的时间戳与 end_time 相差在 15 秒内时才移除
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  from typing import Optional
3
3
  import boto3
4
- from botocore.exceptions import ClientError
5
4
  from loguru import logger
6
5
 
7
6
 
@@ -35,6 +34,6 @@ class S3Client:
35
34
  file_url = f"{self.base_url}/{s3_file_name}"
36
35
  logger.info(f"文件已成功上传到 S3: {file_url}")
37
36
  return file_url
38
- except ClientError as e:
37
+ except Exception as e:
39
38
  logger.error(f"上传文件到 S3 时出错: {e}")
40
39
  return None
@@ -0,0 +1,68 @@
1
+ from typing import Any, NamedTuple
2
+ from .bytecloud import ByteCloudClient
3
+ import requests
4
+ from loguru import logger
5
+
6
+
7
+ class GatewayInfo(NamedTuple):
8
+ mgr_name: str
9
+ ctrl_name: str
10
+ gateway_endpoint: str
11
+ auth_user: str
12
+ auth_password: str
13
+ root_secret_key: str
14
+ gw_meta_db: str
15
+
16
+
17
+ class SpaceXClient:
18
+ """
19
+ SpaceX 通知服务客户端,用于发送飞书消息
20
+ """
21
+
22
+ def __init__(self, bytecloud_client: ByteCloudClient):
23
+ """
24
+ 初始化 SpaceX 通知客户端
25
+ """
26
+ self.bytecloud_client = bytecloud_client
27
+
28
+ def list_mgr(self, site: str) -> list[Any]:
29
+ site_info = self.bytecloud_client.get_site_info(site)
30
+ url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/queryServerMeta"
31
+
32
+ try:
33
+ response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site))
34
+ response.raise_for_status()
35
+
36
+ result = response.json()
37
+ return result.get("data", [])
38
+
39
+ except Exception as e:
40
+ logger.warning(f"do quest queryServerMeta exception: {e}")
41
+ raise
42
+
43
+ def registry_gateway(self, site: str, gateway_info: GatewayInfo) -> bool:
44
+ site_info = self.bytecloud_client.get_site_info(site)
45
+ url = f"{site_info.endpoint_bytedts_spacex}/bytedts/v1/registryGateway"
46
+ data_raw = {
47
+ "server_region": gateway_info.mgr_name,
48
+ "cluster_region": gateway_info.ctrl_name,
49
+ "cluster_name": gateway_info.ctrl_name,
50
+ "server_domain": gateway_info.gateway_endpoint,
51
+ "frontend_user": gateway_info.auth_user,
52
+ "gateway_user": gateway_info.auth_user,
53
+ "root_secret_key": gateway_info.root_secret_key,
54
+ "gateway_password": gateway_info.auth_password,
55
+ "frontend_password": gateway_info.auth_password,
56
+ "gw_meta_db": gateway_info.gw_meta_db,
57
+ "gateway_type": "psm",
58
+ }
59
+ try:
60
+ response = requests.post(url, json=data_raw, headers=self.bytecloud_client.build_request_headers(site))
61
+ response.raise_for_status()
62
+
63
+ result = response.json()
64
+ return result.get("message", "") == "ok"
65
+
66
+ except Exception as e:
67
+ logger.warning(f"do quest registryGateway exception: {e}")
68
+ raise
@@ -0,0 +1,55 @@
1
+ """TCC (Toutiao Config Center) API client."""
2
+
3
+ from typing import Any
4
+ import requests
5
+
6
+ from dtsdance.bytecloud import ByteCloudClient
7
+ from dtsdance.tcc_open import TCCError
8
+
9
+
10
+ class TCCInnerClient:
11
+ """Client for TCC OpenAPI."""
12
+
13
+ def __init__(self, bytecloud_client: ByteCloudClient) -> None:
14
+ self.bytecloud_client = bytecloud_client
15
+
16
+ def list_configs(
17
+ self,
18
+ site: str,
19
+ ns_name: str,
20
+ region: str,
21
+ dir: str,
22
+ conf_name: str,
23
+ ) -> dict[str, Any]:
24
+ """
25
+ List TCC configurations.
26
+ """
27
+ site_info = self.bytecloud_client.get_site_info(site)
28
+ url = f"{site_info.endpoint}/api/v3/tcc/bcc/config/list_v2"
29
+
30
+ data_raw = {
31
+ "ns_name": ns_name,
32
+ "region": region,
33
+ "dir_path": dir,
34
+ "keyword": conf_name,
35
+ "env": "prod",
36
+ "scope": "all",
37
+ "condition": "name",
38
+ "pn": 1,
39
+ "rn": 100,
40
+ }
41
+
42
+ try:
43
+ response = requests.post(url, headers=self.bytecloud_client.build_request_headers(site), json=data_raw, timeout=30)
44
+ response.raise_for_status()
45
+ result = response.json()
46
+
47
+ # Check for errors in response
48
+ base_resp = result.get("base_resp", {})
49
+ if base_resp.get("error_code", 0) != 0:
50
+ raise TCCError(f"TCC API error: {base_resp.get('error_message', 'Unknown error')}")
51
+
52
+ return result.get("data", {})
53
+
54
+ except requests.RequestException as e:
55
+ raise TCCError(f"Failed to get TCC config: {str(e)}") from e
@@ -1,4 +1,4 @@
1
- """TCE (Toutiao Cloud Engine) API client."""
1
+ """TCC (Toutiao Config Center) API client."""
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from typing import Any
@@ -24,11 +24,14 @@ class TCCError(Exception):
24
24
  class TCCClient:
25
25
  """Client for TCC OpenAPI."""
26
26
 
27
- def __init__(self, svc_account: str, svc_secret: str, base_url: str):
27
+ def __init__(self, svc_account: str, svc_secret: str, endpoint: str):
28
28
  self.svc_account = svc_account
29
- self.base_url = base_url
30
- self.headers = {
31
- "Authorization": f"Bearer {svc_secret}",
29
+ self.svc_secret = svc_secret
30
+ self.endpoint = endpoint
31
+
32
+ def _build_headers(self) -> dict[str, str]:
33
+ return {
34
+ "Authorization": f"Bearer {self.svc_secret}",
32
35
  }
33
36
 
34
37
  def publish_config(
@@ -50,7 +53,7 @@ class TCCClient:
50
53
  Raises:
51
54
  TCCError: If API call fails
52
55
  """
53
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/update"
56
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/update"
54
57
 
55
58
  payload = {
56
59
  "ns_name": ns_name,
@@ -67,7 +70,7 @@ class TCCClient:
67
70
 
68
71
  try:
69
72
  # print(f"writing tcc config, payload: {payload}")
70
- response = requests.post(url, headers=self.headers, json=payload, timeout=30)
73
+ response = requests.post(url, headers=self._build_headers(), json=payload, timeout=30)
71
74
  if response.status_code != 200:
72
75
  raise TCCError(f"TCC API request failed with status {response.status_code}\n" f"Response: {response.text}")
73
76
 
@@ -108,7 +111,7 @@ class TCCClient:
108
111
  Raises:
109
112
  TCCError: If API call fails
110
113
  """
111
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/list"
114
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/list"
112
115
 
113
116
  params = {
114
117
  "ns_name": ns_name,
@@ -120,7 +123,7 @@ class TCCClient:
120
123
  }
121
124
 
122
125
  try:
123
- response = requests.post(url, headers=self.headers, params=params, timeout=30)
126
+ response = requests.post(url, headers=self._build_headers(), params=params, timeout=30)
124
127
  response.raise_for_status()
125
128
  result = response.json()
126
129
 
@@ -191,8 +194,8 @@ class TCCClient:
191
194
  self,
192
195
  ns_name: str,
193
196
  region: str,
194
- conf_name: str,
195
197
  dir: str,
198
+ conf_name: str,
196
199
  ) -> dict[str, Any]:
197
200
  """
198
201
  Get TCC configuration.
@@ -209,7 +212,7 @@ class TCCClient:
209
212
  Raises:
210
213
  TCCError: If API call fails
211
214
  """
212
- url = f"{self.base_url}/api/v1/tcc_v3_openapi/bcc/open/config/get"
215
+ url = f"{self.endpoint}/api/v1/tcc_v3_openapi/bcc/open/config/get"
213
216
 
214
217
  params = {
215
218
  "ns_name": ns_name,
@@ -219,7 +222,7 @@ class TCCClient:
219
222
  }
220
223
 
221
224
  try:
222
- response = requests.get(url, headers=self.headers, params=params, timeout=30)
225
+ response = requests.get(url, headers=self._build_headers(), params=params, timeout=30)
223
226
  response.raise_for_status()
224
227
  result = response.json()
225
228
 
@@ -4,15 +4,16 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dts-dance"
7
- version = "0.1.8"
7
+ version = "0.2.0"
8
8
  description = "dts dance lib"
9
9
  readme = "README.md"
10
10
  keywords = ["tools", "observation"]
11
11
  requires-python = ">=3.12"
12
12
  dependencies = [
13
- "requests==2.32.5",
14
- "loguru==0.7.3",
15
- "pyyaml==6.0.3",
13
+ "requests>=2.32.5,<3.0.0",
14
+ "loguru>=0.7.3,<0.8.0",
15
+ "boto3>=1.42.27,<2.0.0",
16
+ "pyyaml>=6.0.3,<7.0.0",
16
17
  ]
17
18
 
18
19
  [tool.hatch.build.targets.wheel]
@@ -20,4 +21,4 @@ packages = ["dtsdance"]
20
21
 
21
22
  [tool.black]
22
23
  line-length = 150
23
- target-version = ['py312']
24
+ target-version = ['py312']
@@ -1,9 +1,10 @@
1
1
  import os
2
2
  from typing import Any
3
3
  import yaml
4
+ from dtsdance.bytecloud import SiteConfig
4
5
 
5
6
 
6
- def load_config() -> dict[str, Any]:
7
+ def load_site_configs(sites: list[str] | None = None) -> dict[str, SiteConfig]:
7
8
  """从配置文件加载配置
8
9
 
9
10
  Returns:
@@ -14,9 +15,16 @@ def load_config() -> dict[str, Any]:
14
15
  """
15
16
  config_path = os.environ.get("CONFIG_PATH", "config.yaml")
16
17
 
18
+ configs: dict[str, Any] = {}
17
19
  try:
18
20
  with open(config_path, "r", encoding="utf-8") as f:
19
- return yaml.safe_load(f)
21
+ configs = yaml.safe_load(f)
20
22
 
21
23
  except Exception as e:
22
24
  raise ValueError(f"加载配置失败: {e}")
25
+
26
+ return {
27
+ k: SiteConfig(k, v["endpoint"], v["svc_account"], v["svc_secret"], v.get("endpoint_bytedts_spacex", None))
28
+ for k, v in configs["sites"].items()
29
+ if k in sites
30
+ }
@@ -0,0 +1,14 @@
1
+ from dtsdance.dflow import DFlowClient
2
+ from dtsdance.bytecloud import ByteCloudClient
3
+
4
+ from config import load_site_configs
5
+
6
+ site_configs = load_site_configs(["boe"])
7
+ bytecloud_client = ByteCloudClient(site_configs)
8
+ dflow_client = DFlowClient(bytecloud_client)
9
+
10
+
11
+ # pytest tests/test_dflow.py::test_get_dflow_info -s
12
+ def test_get_dflow_info():
13
+ info = dflow_client.get_dflow_info("boe", "1324653150464")
14
+ print(f"DFlow Info: {info}")
@@ -0,0 +1,30 @@
1
+ import json
2
+ from dtsdance.bytecloud import ByteCloudClient
3
+ from dtsdance.spacex import GatewayInfo, SpaceXClient
4
+ from config import load_site_configs
5
+
6
+ site = "boe"
7
+ site_configs = load_site_configs([site])
8
+ bytecloud_client = ByteCloudClient(site_configs)
9
+ spacex = SpaceXClient(bytecloud_client)
10
+
11
+
12
+ # pytest tests/test_spacex.py::test_list_mgr -s
13
+ def test_list_mgr():
14
+ mgr_list = spacex.list_mgr(site)
15
+ print(f"mgr_list: {json.dumps(mgr_list, indent=2, ensure_ascii=False)}")
16
+
17
+
18
+ # pytest tests/test_spacex.py::test_registry_gateway -s
19
+ def test_registry_gateway():
20
+ gateway_info = GatewayInfo(
21
+ mgr_name="boe",
22
+ ctrl_name="boe_halo_test",
23
+ gateway_endpoint="volc.dts.gateway.service.boe:cluster:boe_halo_test:family:v6",
24
+ auth_user="bytedts_backend",
25
+ auth_password="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJieXRlZHRzX2JhY2tlbmQiLCJleHAiOjQ4OTEzMzQ0MDAsImRvbWFpbiI6IioifQ.D_87X-JCQn1CU9ru3PpeM1lmlOgVki6bVHo-kQ60eio",
26
+ root_secret_key="97oscH5k",
27
+ gw_meta_db="bytedts_sre_halo",
28
+ )
29
+ result = spacex.registry_gateway(site, gateway_info)
30
+ print(f"result: {result}")
@@ -0,0 +1,22 @@
1
+ from config import load_site_configs
2
+ from dtsdance.bytecloud import ByteCloudClient
3
+ from dtsdance.tcc_open import TCCClient
4
+ from dtsdance.tcc_inner import TCCInnerClient
5
+
6
+ site_configs = load_site_configs(["boe", "us-ttp"])
7
+ bytecloud_client = ByteCloudClient(site_configs)
8
+ client_inner = TCCInnerClient(bytecloud_client)
9
+
10
+
11
+ # pytest tests/test_tcc.py::test_get_config -s
12
+ def test_get_config():
13
+ site_info = site_configs["boe"]
14
+ client_open = TCCClient(site_info.svc_account, site_info.svc_secret, endpoint=site_info.endpoint)
15
+ config_ctrl_env = client_open.get_config(ns_name="bytedts.mgr.api", region="China-BOE", dir="/default", conf_name="ctrl_env")
16
+ print(f"ctrl_env: {config_ctrl_env}")
17
+
18
+
19
+ # pytest tests/test_tcc.py::test_list_configs -s
20
+ def test_list_configs():
21
+ configs = client_inner.list_configs(site="us-ttp", ns_name="bytedts.mgr.api", region="US-TTP", dir="/default", conf_name="ctrl_env")
22
+ print(f"configs: {configs}")
@@ -2,6 +2,34 @@ version = 1
2
2
  revision = 3
3
3
  requires-python = ">=3.12"
4
4
 
5
+ [[package]]
6
+ name = "boto3"
7
+ version = "1.42.27"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ dependencies = [
10
+ { name = "botocore" },
11
+ { name = "jmespath" },
12
+ { name = "s3transfer" },
13
+ ]
14
+ sdist = { url = "https://files.pythonhosted.org/packages/8d/99/65569052c911160702ad371b0b08b751bb1df29deeef0c5c117528074c29/boto3-1.42.27.tar.gz", hash = "sha256:a8a53abb98ff1a24d9a88d9d8c0285bf02d23189666130456e8951ede2f7db98", size = 112765, upload-time = "2026-01-13T20:35:11.971Z" }
15
+ wheels = [
16
+ { url = "https://files.pythonhosted.org/packages/44/9e/9554a8c1d7610ec8ea55bec0dac87ad894c83cada2ea2c05fc45f14d0cde/boto3-1.42.27-py3-none-any.whl", hash = "sha256:39dfec51aff3f9356e8c7331195f324cb498ec75b2601a902fc62aa127b8fd00", size = 140577, upload-time = "2026-01-13T20:35:09.35Z" },
17
+ ]
18
+
19
+ [[package]]
20
+ name = "botocore"
21
+ version = "1.42.27"
22
+ source = { registry = "https://pypi.org/simple" }
23
+ dependencies = [
24
+ { name = "jmespath" },
25
+ { name = "python-dateutil" },
26
+ { name = "urllib3" },
27
+ ]
28
+ sdist = { url = "https://files.pythonhosted.org/packages/65/90/55b003d38f947c90c0d7e306d377dcdfd9cd0dc1e184082b2d1a6adb0eec/botocore-1.42.27.tar.gz", hash = "sha256:c8e1e3ffb6c871622b1c8054f064d60cbc786aa5ca1f97f5f9fd5fa0a9d82d05", size = 14880030, upload-time = "2026-01-13T20:35:00.31Z" }
29
+ wheels = [
30
+ { url = "https://files.pythonhosted.org/packages/bf/32/8a4a0447432425cd2f772c757d988742685f46796cf5d68aeaf6bcb6bc37/botocore-1.42.27-py3-none-any.whl", hash = "sha256:d51fb3b8dd1a944c8d238d2827a0dd6e5528d6da49a3bd9eccad019c533e4c9c", size = 14555236, upload-time = "2026-01-13T20:34:55.918Z" },
31
+ ]
32
+
5
33
  [[package]]
6
34
  name = "certifi"
7
35
  version = "2025.11.12"
@@ -79,9 +107,10 @@ wheels = [
79
107
 
80
108
  [[package]]
81
109
  name = "dts-dance"
82
- version = "0.1.8"
110
+ version = "0.2.0"
83
111
  source = { editable = "." }
84
112
  dependencies = [
113
+ { name = "boto3" },
85
114
  { name = "loguru" },
86
115
  { name = "pyyaml" },
87
116
  { name = "requests" },
@@ -89,9 +118,10 @@ dependencies = [
89
118
 
90
119
  [package.metadata]
91
120
  requires-dist = [
92
- { name = "loguru", specifier = "==0.7.3" },
93
- { name = "pyyaml", specifier = "==6.0.3" },
94
- { name = "requests", specifier = "==2.32.5" },
121
+ { name = "boto3", specifier = ">=1.42.27,<2.0.0" },
122
+ { name = "loguru", specifier = ">=0.7.3,<0.8.0" },
123
+ { name = "pyyaml", specifier = ">=6.0.3,<7.0.0" },
124
+ { name = "requests", specifier = ">=2.32.5,<3.0.0" },
95
125
  ]
96
126
 
97
127
  [[package]]
@@ -103,6 +133,15 @@ wheels = [
103
133
  { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
104
134
  ]
105
135
 
136
+ [[package]]
137
+ name = "jmespath"
138
+ version = "1.0.1"
139
+ source = { registry = "https://pypi.org/simple" }
140
+ sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" }
141
+ wheels = [
142
+ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
143
+ ]
144
+
106
145
  [[package]]
107
146
  name = "loguru"
108
147
  version = "0.7.3"
@@ -116,6 +155,18 @@ wheels = [
116
155
  { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
117
156
  ]
118
157
 
158
+ [[package]]
159
+ name = "python-dateutil"
160
+ version = "2.9.0.post0"
161
+ source = { registry = "https://pypi.org/simple" }
162
+ dependencies = [
163
+ { name = "six" },
164
+ ]
165
+ sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
166
+ wheels = [
167
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
168
+ ]
169
+
119
170
  [[package]]
120
171
  name = "pyyaml"
121
172
  version = "6.0.3"
@@ -177,6 +228,27 @@ wheels = [
177
228
  { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
178
229
  ]
179
230
 
231
+ [[package]]
232
+ name = "s3transfer"
233
+ version = "0.16.0"
234
+ source = { registry = "https://pypi.org/simple" }
235
+ dependencies = [
236
+ { name = "botocore" },
237
+ ]
238
+ sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" }
239
+ wheels = [
240
+ { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" },
241
+ ]
242
+
243
+ [[package]]
244
+ name = "six"
245
+ version = "1.17.0"
246
+ source = { registry = "https://pypi.org/simple" }
247
+ sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
248
+ wheels = [
249
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
250
+ ]
251
+
180
252
  [[package]]
181
253
  name = "urllib3"
182
254
  version = "2.6.2"
@@ -1,5 +0,0 @@
1
- sites:
2
- test:
3
- endpoint: xxx
4
- svc_account: xxx
5
- svc_secret: xxx
@@ -1,69 +0,0 @@
1
- from .bytecloud import ByteCloudHelper
2
- import requests
3
- import json
4
- from typing import Dict, List
5
- from loguru import logger
6
-
7
-
8
- class SpaceXNotifier:
9
- """
10
- SpaceX 通知服务客户端,用于发送飞书消息
11
- """
12
-
13
- # 飞书机器人 webhook URL
14
- _SPACEX_DOMAIN = {
15
- "BOE": "spacex-api-boe.byted.org",
16
- "China-North": "spacex-api.byted.org",
17
- "Singapore-Central": "spacex-api-i18n.byted.org",
18
- "EUTTP": "spacex-api.tiktoke.org",
19
- }
20
-
21
- def __init__(self, bytecloud_helper: ByteCloudHelper):
22
- """
23
- 初始化 SpaceX 通知客户端
24
- """
25
- self.bytecloud_helper = bytecloud_helper
26
-
27
- def send_feishu_message(self, env: str, feishu_content: Dict, feishu_groups: List[str]) -> str:
28
- """
29
- 发送飞书消息
30
-
31
- Args:
32
- feishu_content: 飞书消息内容,包含卡片配置
33
- feishu_groups: 飞书群组 ID 列表
34
-
35
- Returns:
36
- str: 响应内容
37
-
38
- Raises:
39
- Exception: 请求失败时抛出异常
40
- """
41
- logger.debug(f"send_feishu_message env: {env}, feishu_content: {str(feishu_content)[:100]}..., feishu_groups: {feishu_groups}")
42
-
43
- body = {
44
- "product_name": "ByteDTS-Inspector",
45
- "notification_detail": [
46
- {
47
- "channel": "Lark",
48
- "content": json.dumps(feishu_content),
49
- "lark_groups": feishu_groups,
50
- }
51
- ],
52
- }
53
-
54
- # 发送 HTTP POST 请求
55
- try:
56
- url = f"https://{self._SPACEX_DOMAIN[env]}/notification_service/api/v1/messages"
57
- headers = {
58
- "Content-Type": "application/json",
59
- "X-Jwt-Token": self.bytecloud_helper.get_jwt_token(env),
60
- }
61
-
62
- response = requests.post(url, json=body, headers=headers)
63
- response.raise_for_status()
64
-
65
- print("response: {}".format(response.text))
66
- return response.text
67
- except Exception as e:
68
- print("发送消息时出错: {}".format(e))
69
- raise
@@ -1,21 +0,0 @@
1
- from dtsdance.dflow import DFlowHelper
2
- from dtsdance.bytecloud import ByteCloudEnvInfo, ByteCloudHelper
3
-
4
- from config import load_config
5
-
6
- config = load_config()
7
- envs_bytecloud = {k: ByteCloudEnvInfo(k, v["endpoint"], v["svc_secret"]) for k, v in config["sites"].items()}
8
- bytecloud_helper = ByteCloudHelper(envs_bytecloud)
9
- dsyncer_helper = DFlowHelper(bytecloud_helper)
10
-
11
-
12
- # pytest tests/test_dflow.py::test_get_dflow_info -s
13
- def test_get_dflow_info():
14
- try:
15
- info = dsyncer_helper.get_dflow_info("boe", "1324653150464")
16
- # info = dsyncer_helper.get_dflow_info("i18n-bd", "4102917472017")
17
- # info = dsyncer_helper.get_dflow_info("us-ttp", "257993088799750")
18
- # info = dsyncer_helper.get_dflow_info("eu-ttp", "815571859216")
19
- print(f"DFlow Info: {info}")
20
- except Exception as e:
21
- print(f"Failed to get DFlow info for environment boe: {e}")
File without changes
File without changes
File without changes
File without changes