1717
1818import logging
1919from os import PathLike , getenv
20- from typing import Any , BinaryIO , List
20+ from typing import Any , BinaryIO , List , Optional
2121from urllib .parse import urljoin
2222
2323import curlify
@@ -69,24 +69,39 @@ class RepositoryType(str, StructuredEnum):
6969
7070
7171class 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
194216class 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" )
0 commit comments