Skip to content

Commit c385619

Browse files
authored
feat: 多租户模式下,bkrepo 管理端 API 请求头添加租户 ID,项目 ID 添加租户 ID 前缀 (TencentBlueKing#217)
1 parent 7b58a40 commit c385619

6 files changed

Lines changed: 142 additions & 54 deletions

File tree

sdks/blue-krill/CHANGE.md

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

3+
### 2.1.1
4+
- Feature: BKRepoManager 新增参数 tenant_id,传了该参数则添加请求头:X-Bk-Tenant-Id
5+
- Feature: BKRepoManager.create_project 的参数 project 修改为:project_name
6+
37
### 2.1.0
48

59
- Feature: improve editionctl tool in various places: debounce, conditional linking and event filtering

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.0"
18+
__version__ = "2.1.1"

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

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import logging
1919
from os import PathLike, getenv
20-
from typing import Any, BinaryIO, List
20+
from typing import Any, BinaryIO, List, Optional
2121
from urllib.parse import urljoin
2222

2323
import curlify
@@ -69,24 +69,39 @@ class RepositoryType(str, StructuredEnum):
6969

7070

7171
class BKRepoManager:
72-
"""蓝鲸 bkrepo 管理端"""
72+
"""蓝鲸 bkrepo 管理端,提供创建项目、仓库、用户等功能。
73+
74+
使用说明:多租户模式下需要传入租户 ID。
75+
示例:
76+
>>> manager = BKRepoManager(
77+
... endpoint_url="http://bkrepo.example.com",
78+
... username="admin",
79+
... password="blueking",
80+
... tenant_id="tenant-123" # 多租户模式必填
81+
... )
82+
>>> manager.create_project("myproject") # 按目前 bk-repo 的规则,非多租户模式下,创建后的项目 ID 为 "myproject"; 多租户模式下, 创建后的项目ID为 "tenant-123-myproject"
83+
"""
7384

7485
def __init__(
7586
self,
7687
endpoint_url: str,
7788
username: str,
7889
password: str,
90+
tenant_id: Optional[str] = None,
7991
**kwargs,
8092
):
8193
# endpoint can not endswith '/'
8294
self.endpoint_url = endpoint_url.rstrip("/")
8395
self.username = username
8496
self.password = password
97+
self.tenant_id = tenant_id
8598
self._max_retries = kwargs.get("max_retries", MAX_RETRIES)
8699

87100
def get_client(self) -> requests.Session:
88101
session = requests.session()
89102
session.auth = HTTPBasicAuth(username=self.username, password=self.password)
103+
if self.tenant_id:
104+
session.headers.update({"X-Bk-Tenant-Id": self.tenant_id})
90105
session.mount("http://", HTTPAdapter(max_retries=self._max_retries))
91106
session.mount("https://", HTTPAdapter(max_retries=self._max_retries))
92107
return session
@@ -96,10 +111,11 @@ def create_user_to_repo(
96111
) -> bool:
97112
"""创建用户到仓库管理员
98113
99-
:params username str: 用户名
100-
:params password str: 密码
101-
:params association_users List[str]: 关联的真实用户
102-
:params repo str: 关联的仓库名称
114+
:param username: 用户名
115+
:param password: 密码
116+
:param association_users: 关联的真实用户
117+
:param repo: 关联的仓库名称
118+
:param project: 项目 ID
103119
"""
104120
client = self.get_client()
105121
url = urljoin(self.endpoint_url, "/auth/api/user/create/repo")
@@ -131,7 +147,8 @@ def delete_user(self, username: str):
131147
def create_repo(self, project: str, repo: str, repo_type: str = RepositoryType.GENERIC, public: bool = False):
132148
"""创建仓库
133149
134-
:param public bool: 是否公开读, 当 public 为 True 时, 代表公开读私有写; 当 public 为 False 时, 代表私有读写
150+
:param public: 是否公开读, 当 public 为 True 时, 代表公开读私有写; 当 public 为 False 时, 代表私有读写
151+
:param project: 项目 ID
135152
"""
136153
client = self.get_client()
137154
url = urljoin(self.endpoint_url, "/repository/api/repo/create")
@@ -150,32 +167,37 @@ def create_repo(self, project: str, repo: str, repo_type: str = RepositoryType.G
150167
def delete_repo(self, project: str, repo: str, forced: bool = False):
151168
"""删除仓库
152169
153-
:params repo str: 仓库名
154-
:params forced bool: 是否强制删除, 如果为false,当仓库中存在文件时,将无法删除仓库
170+
:param project: 项目 ID
171+
:param repo: 仓库名
172+
:param forced: 是否强制删除, 如果为false,当仓库中存在文件时,将无法删除仓库
155173
"""
156174
client = self.get_client()
157175
url = urljoin(self.endpoint_url, f"/repository/api/repo/delete/{project}/{repo}?forced={forced}")
158176
return _validate_resp(client.delete(url, timeout=TIMEOUT_THRESHOLD))
159177

160178
# 以下是项目无关的管理接口
161179

162-
def create_project(self, project: str):
180+
def create_project(self, project_name: str):
163181
"""创建项目
164182
165-
:params project str: 项目名
183+
:param project_name: 项目名称
166184
"""
167185
client = self.get_client()
168186
url = urljoin(self.endpoint_url, "/repository/api/project/create")
169-
data = {"name": project, "displayName": project, "description": ""}
187+
# Note: 创建项目时传的是项目名称,创建成功后 API 未返回项目 ID 信息
188+
# 按目前 bk-repo 的规则,启用/关闭多租户模式的情况下:
189+
# 关闭多租户: 项目 ID == 项目名称
190+
# 启用多租户: 项目 ID == f"{租户 ID}_{项目名称}"
191+
data = {"name": project_name, "displayName": project_name, "description": ""}
170192
return _validate_resp(client.post(url, json=data, timeout=TIMEOUT_THRESHOLD))
171193

172194
def create_user_to_project(self, username: str, password: str, association_users: List[str], project: str) -> bool:
173195
"""创建用户到项目管理员
174196
175-
:params username str: 用户名
176-
:params password str: 密码
177-
:params association_users List[str]: 关联的真实用户
178-
:params project str: 关联的项目名称
197+
:param username: 用户名
198+
:param password: 密码
199+
:param association_users: 关联的真实用户
200+
:param project: 项目 ID
179201
"""
180202
client = self.get_client()
181203
url = urljoin(self.endpoint_url, "/auth/api/user/create/project")
@@ -192,7 +214,7 @@ def create_user_to_project(self, username: str, password: str, association_users
192214

193215

194216
class BKGenericRepo(BlobStore):
195-
"""蓝鲸通用二进制仓库."""
217+
"""蓝鲸通用二进制仓库,提供通用制品上传、下载、删除等功能。"""
196218

197219
STORE_TYPE = "bkrepo"
198220

@@ -223,19 +245,19 @@ def get_client(self) -> requests.Session:
223245
def upload_file(self, filepath: PathLike, key: str, allow_overwrite: bool = True, **kwargs):
224246
"""上传通用制品文件
225247
226-
:param PathLike filepath: 需要上传文件的路径
227-
:param str key: 文件完整路径
228-
:param bool allow_overwrite: 是否覆盖已存在文件
248+
:param PathLike: 需要上传文件的路径
249+
:param key: 文件完整路径
250+
:param allow_overwrite: 是否覆盖已存在文件
229251
"""
230252
with open(filepath, "rb") as fh:
231253
self.upload_fileobj(fh, key=key, allow_overwrite=allow_overwrite, timeout=TIMEOUT_THRESHOLD, **kwargs)
232254

233255
def upload_fileobj(self, fh: BinaryIO, key: str, allow_overwrite: bool = True, **kwargs):
234256
"""上传通用制品文件
235257
236-
:param BinaryIO fh: 文件句柄
237-
:param str key: 文件完整路径
238-
:param bool allow_overwrite: 是否覆盖已存在文件
258+
:param fh: 文件句柄
259+
:param key: 文件完整路径
260+
:param allow_overwrite: 是否覆盖已存在文件
239261
"""
240262
client = self.get_client()
241263
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
@@ -259,8 +281,8 @@ def upload_fileobj(self, fh: BinaryIO, key: str, allow_overwrite: bool = True, *
259281
def download_file(self, key: str, filepath: PathLike, *args, **kwargs) -> PathLike:
260282
"""下载通用制品文件
261283
262-
:param str key: 文件完整路径
263-
:param PathLike filepath: 文件下载的路径
284+
:param key: 文件完整路径
285+
:param filepath: 文件下载的路径
264286
"""
265287
with open(filepath, mode="wb") as fh:
266288
self.download_fileobj(key, fh)
@@ -269,8 +291,8 @@ def download_file(self, key: str, filepath: PathLike, *args, **kwargs) -> PathLi
269291
def download_fileobj(self, key: str, fh, *args, **kwargs):
270292
"""下载通用制品文件
271293
272-
:param str key: 文件完整路径
273-
:param IO fh: 文件句柄
294+
:param key: 文件完整路径
295+
:param fh: 文件句柄
274296
"""
275297
client = self.get_client()
276298
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
@@ -302,7 +324,7 @@ def download_fileobj(self, key: str, fh, *args, **kwargs):
302324
def delete_file(self, key: str, *args, **kwargs):
303325
"""删除通用制品文件
304326
305-
:param str key: 文件完整路径
327+
:param key: 文件完整路径
306328
"""
307329
client = self.get_client()
308330
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
@@ -312,7 +334,7 @@ def delete_file(self, key: str, *args, **kwargs):
312334
def get_file_metadata(self, key, *args, **kwargs):
313335
"""获取通用制品文件头部信息
314336
315-
:param str key: 文件完整路径
337+
:param key: 文件完整路径
316338
"""
317339
client = self.get_client()
318340
url = urljoin(self.endpoint_url, f"/generic/{self.project}/{self.bucket}/{key}")
@@ -326,10 +348,10 @@ def generate_presigned_url(
326348
) -> str:
327349
"""创建临时访问url
328350
329-
:param str key: 授权路径
330-
:param int expires_in: token 有效时间,单位秒,小于等于 0 则永久有效
331-
:param str signature_type: 签名类型。UPLOAD:允许上传, DOWNLOAD: 允许下载。
332-
:param str token_type: [deprecated] token类型。UPLOAD:允许上传, DOWNLOAD: 允许下载, ALL: 同时允许上传和下载。
351+
:param key: 授权路径
352+
:param expires_in: token 有效时间,单位秒,小于等于 0 则永久有效
353+
:param signature_type: 签名类型。UPLOAD:允许上传, DOWNLOAD: 允许下载。
354+
:param token_type: [deprecated] token类型。UPLOAD:允许上传, DOWNLOAD: 允许下载, ALL: 同时允许上传和下载。
333355
"""
334356
client = self.get_client()
335357
url = urljoin(self.endpoint_url, "/generic/temporary/url/create")

sdks/blue-krill/poetry.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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.0"
12+
version = "2.1.1"
1313
# classifieres is dynamic because we want to create Python classifiers automatically
1414
dynamic = ["classifiers"]
1515
readme = "README.md"

0 commit comments

Comments
 (0)