Skip to content

Commit 478fe19

Browse files
authored
fix: bKRepoManager not support endpoint_url with url path (TencentBlueKing#246)
1 parent a6416a1 commit 478fe19

4 files changed

Lines changed: 35 additions & 14 deletions

File tree

sdks/blue-krill/CHANGE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## Change logs
22

3+
### 2.1.5
4+
5+
- Fix: BKRepoManager 和 BKGenericRepo 不支持 endpoint_url 带子路径的问题
6+
7+
38
### 2.1.4
49

510
- Fix: `TaskPoller.__init_subclass__``CallbackHandler.__init_subclass__` 中同名子类注册覆盖的问题

sdks/blue-krill/blue_krill/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@
1515
# We undertake not to change the open source license (MIT license) applicable
1616
# to the current version of the project delivered to anyone in the future.
1717

18-
__version__ = "2.1.4"
18+
__version__ = "2.1.5"

sdks/blue-krill/blue_krill/storages/blobstore/bkrepo.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def create_user_to_repo(
118118
:param project: 项目 ID
119119
"""
120120
client = self.get_client()
121-
url = urljoin(self.endpoint_url, "/auth/api/user/create/repo")
121+
url = safe_urljoin(self.endpoint_url, "auth/api/user/create/repo")
122122
data = {
123123
"admin": False,
124124
"name": username,
@@ -134,14 +134,14 @@ def create_user_to_repo(
134134
def update_user(self, username: str, password: str, association_users: List[str]):
135135
"""更新用户信息"""
136136
client = self.get_client()
137-
url = urljoin(self.endpoint_url, f"/auth/api/user/{username}")
137+
url = safe_urljoin(self.endpoint_url, f"auth/api/user/{username}")
138138
data = {"admin": False, "name": username, "pwd": password, "asstUsers": association_users}
139139
return _validate_resp(client.put(url, json=data, timeout=TIMEOUT_THRESHOLD))
140140

141141
def delete_user(self, username: str):
142142
"""删除用户"""
143143
client = self.get_client()
144-
url = urljoin(self.endpoint_url, f"/auth/api/user/{username}")
144+
url = safe_urljoin(self.endpoint_url, f"auth/api/user/{username}")
145145
return _validate_resp(client.delete(url, timeout=TIMEOUT_THRESHOLD))
146146

147147
def create_repo(self, project: str, repo: str, repo_type: str = RepositoryType.GENERIC, public: bool = False):
@@ -151,7 +151,7 @@ def create_repo(self, project: str, repo: str, repo_type: str = RepositoryType.G
151151
:param project: 项目 ID
152152
"""
153153
client = self.get_client()
154-
url = urljoin(self.endpoint_url, "/repository/api/repo/create")
154+
url = safe_urljoin(self.endpoint_url, "repository/api/repo/create")
155155
data = {
156156
"projectId": project,
157157
"name": repo,
@@ -172,7 +172,7 @@ def delete_repo(self, project: str, repo: str, forced: bool = False):
172172
:param forced: 是否强制删除, 如果为false,当仓库中存在文件时,将无法删除仓库
173173
"""
174174
client = self.get_client()
175-
url = urljoin(self.endpoint_url, f"/repository/api/repo/delete/{project}/{repo}?forced={forced}")
175+
url = safe_urljoin(self.endpoint_url, f"repository/api/repo/delete/{project}/{repo}?forced={forced}")
176176
return _validate_resp(client.delete(url, timeout=TIMEOUT_THRESHOLD))
177177

178178
# 以下是项目无关的管理接口
@@ -183,7 +183,7 @@ def create_project(self, project_name: str):
183183
:param project_name: 项目名称
184184
"""
185185
client = self.get_client()
186-
url = urljoin(self.endpoint_url, "/repository/api/project/create")
186+
url = safe_urljoin(self.endpoint_url, "repository/api/project/create")
187187
# Note: 创建项目时传的是项目名称,创建成功后 API 未返回项目 ID 信息
188188
# 按目前 bk-repo 的规则,启用/关闭多租户模式的情况下:
189189
# 关闭多租户: 项目 ID == 项目名称
@@ -200,7 +200,7 @@ def create_user_to_project(self, username: str, password: str, association_users
200200
:param project: 项目 ID
201201
"""
202202
client = self.get_client()
203-
url = urljoin(self.endpoint_url, "/auth/api/user/create/project")
203+
url = safe_urljoin(self.endpoint_url, "auth/api/user/create/project")
204204
data = {
205205
"admin": False,
206206
"name": username,
@@ -260,7 +260,7 @@ def upload_fileobj(self, fh: BinaryIO, key: str, allow_overwrite: bool = True, *
260260
:param allow_overwrite: 是否覆盖已存在文件
261261
"""
262262
client = self.get_client()
263-
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
263+
url = safe_urljoin(self.endpoint_url, f"generic/{self.project}/{self.bucket}/{key}")
264264
src = getattr(fh, "name", "<memory>")
265265
headers = {"X-BKREPO-OVERWRITE": str(allow_overwrite)}
266266

@@ -295,7 +295,7 @@ def download_fileobj(self, key: str, fh, *args, **kwargs):
295295
:param fh: 文件句柄
296296
"""
297297
client = self.get_client()
298-
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
298+
url = safe_urljoin(self.endpoint_url, f"generic/{self.project}/{self.bucket}/{key}")
299299
dest = getattr(fh, "name", "<memory>")
300300
try:
301301
resp = client.get(url, stream=True, timeout=TIMEOUT_THRESHOLD)
@@ -327,7 +327,7 @@ def delete_file(self, key: str, *args, **kwargs):
327327
:param key: 文件完整路径
328328
"""
329329
client = self.get_client()
330-
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
330+
url = safe_urljoin(self.endpoint_url, f"generic/{self.project}/{self.bucket}/{key}")
331331
resp = client.delete(url, timeout=TIMEOUT_THRESHOLD)
332332
return _validate_resp(resp)
333333

@@ -337,7 +337,7 @@ def get_file_metadata(self, key, *args, **kwargs):
337337
:param key: 文件完整路径
338338
"""
339339
client = self.get_client()
340-
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
340+
url = safe_urljoin(self.endpoint_url, f"generic/{self.project}/{self.bucket}/{key}")
341341
resp = client.head(url, timeout=TIMEOUT_THRESHOLD)
342342
if resp.status_code == 200:
343343
return resp.headers
@@ -354,7 +354,7 @@ def generate_presigned_url(
354354
:param token_type: [deprecated] token类型。UPLOAD:允许上传, DOWNLOAD: 允许下载, ALL: 同时允许上传和下载。
355355
"""
356356
client = self.get_client()
357-
url = urljoin(self.endpoint_url, "/generic/temporary/url/create")
357+
url = safe_urljoin(self.endpoint_url, "generic/temporary/url/create")
358358

359359
token_type = signature_type.value
360360
if "token_type" in kwargs:
@@ -374,3 +374,19 @@ def generate_presigned_url(
374374
)
375375
data = _validate_resp(resp)
376376
return data[0]["url"]
377+
378+
379+
def safe_urljoin(base: str, path: str) -> str:
380+
"""
381+
安全拼接 URL,自动处理开头/结尾的斜杠。
382+
即使 path 以 '/' 开头,也会保留 base 的路径。
383+
384+
示例: "http://example.com/base-path" 和 "/custom-path" 可以正确拼接为 "http://example.com/base-path/custom-path",
385+
386+
:param base: 基础 URL
387+
:param path: 要拼接的路径
388+
"""
389+
path = path.lstrip('/')
390+
if not base.endswith('/'):
391+
base += '/'
392+
return urljoin(base, path)

sdks/blue-krill/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "blue-krill"
99
description = "Tools and common packages for blueking PaaS platform."
1010
requires-python = ">=3.9,<3.12"
1111
license = "MIT"
12-
version = "2.1.4"
12+
version = "2.1.5"
1313
# classifieres is dynamic because we want to create Python classifiers automatically
1414
dynamic = ["classifiers"]
1515
readme = "README.md"

0 commit comments

Comments
 (0)