def __init__(self, username: str, bk_supplier_account: Optional[str] = settings. BKCC_DEFAULT_SUPPLIER_ACCOUNT): self._config = BkCCConfig(host=settings.COMPONENT_HOST) self._client = BaseHttpClient( BkCCAuth(username, bk_supplier_account=bk_supplier_account))
def __init__(self, access_token: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None): self._config = BkRepoConfig() self._client = BaseHttpClient( BkRepoAuth(access_token, username, password))
def __init__(self, username: str, access_token: str = None, password: str = None): self._config = BkRepoApigwConfig() self._bk_repo_raw_config = BkRepoRawConfig() self._client = BaseHttpClient( BkRepoAuth(access_token, username, password), )
class SopsClient(BkApiClient): def __init__(self): self._config = SopsConfig(host=settings.SOPS_API_HOST) self._client = BaseHttpClient(SopsAuth()) @response_handler(default=dict) def create_task(self, bk_biz_id: str, template_id: str, data: CreateTaskParams) -> Dict: """通过业务流程创建任务 :param bk_biz_id: 作业模板所属的业务ID :param template_id: 作业模板ID :param data: 创建任务实例需要的参数,包含名称、申请人、业务等 :returns: 返回任务详情,包含任务连接、步骤名称、任务ID """ url = self._config.create_task_url.format(template_id=template_id, bk_biz_id=bk_biz_id) return self._client.request_json("POST", url, json=asdict(data)) @response_handler(default=dict) def start_task(self, bk_biz_id: str, task_id: str) -> Dict: """启动任务 :param bk_biz_id: 作业模板所属的业务ID :param task_id: 任务ID :returns: 返回的数据中,包含任务连接 """ url = self._config.start_task_url.format(task_id=task_id, bk_biz_id=bk_biz_id) return self._client.request_json("POST", url) @response_handler(default=dict) def get_task_status(self, bk_biz_id: str, task_id: str) -> Dict: """获取任务状态 :param bk_biz_id: 作业模板所属的业务ID :param task_id: 任务ID :returns: 返回任务执行状态,包含启动时间、任务状态、子步骤名称、子步骤状态等 """ url = self._config.get_task_status_url.format(task_id=task_id, bk_biz_id=bk_biz_id) return self._client.request_json("GET", url) @response_handler(default=dict) def get_task_node_data(self, bk_biz_id: str, task_id: str, node_id: str) -> Dict: """获取任务步骤的详情 :param bk_biz_id: 作业模板所属的业务ID :param task_id: 任务ID :param node_id: 子步骤的ID :returns: 返回子步骤输出,便于解析输出,从而得到对应的value """ url = self._config.get_task_node_data_url.format(task_id=task_id, bk_biz_id=bk_biz_id) return self._client.request_json("GET", url, params={"node_id": node_id})
def test_default_kwargs(self, requests_mock): requests_mock.get('http://test.com', json={}) client = BaseHttpClient() client.request('GET', 'http://test.com/') req_history = requests_mock.request_history[0] assert req_history.headers.get('X-Request-Id') is not None assert req_history.timeout is not None assert req_history.verify is client._ssl_verify
class PaaSCCClient(BkApiClient): """访问 PaaSCC 服务的 Client 对象 :param auth: 包含校验信息的对象 """ def __init__(self, auth: ComponentAuth): self._config = PaaSCCConfig(host=BCS_CC_API_PRE_URL) self._client = BaseHttpClient(auth.to_header_api_auth()) def get_cluster(self, project_id: str, cluster_id: str) -> Dict: """获取集群信息""" url = self._config.get_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json('GET', url) @parse_response_data() def get_project(self, project_id: str) -> Dict: """获取项目信息""" url = self._config.get_project_url.format(project_id=project_id) return self._client.request_json('GET', url) @response_handler() def update_cluster(self, project_id: str, cluster_id: str, data: Dict) -> Dict: """更新集群信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param data: 更新的集群属性,包含status,名称、描述等 """ url = self._config.update_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("PUT", url, json=data) @response_handler() def delete_cluster(self, project_id: str, cluster_id: str): """删除集群 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID """ url = self._config.delete_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("DELETE", url) @response_handler() def update_node_list(self, project_id: str, cluster_id: str, nodes: List[UpdateNodesData]) -> List: """更新节点信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param nodes: 更新的节点属性,包含IP和状态 :returns: 返回更新的节点信息 """ url = self._config.update_node_list_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("PATCH", url, json={"updates": [asdict(node) for node in nodes]})
def test_status_errors(self, status_code, exc, requests_mock): requests_mock.get('http://test.com', json={'foo': 'bar'}, status_code=status_code) exc_context = pytest.raises(exc) if exc else nullcontext() with exc_context: BaseHttpClient().request('GET', 'http://test.com/')
class PaaSCCClient(BkApiClient): """访问 PaaSCC 服务的 Client 对象 :param auth: 包含校验信息的对象 """ def __init__(self, auth: ComponentAuth): self._config = PaaSCCConfig(host=BCS_CC_API_PRE_URL) self._client = BaseHttpClient(auth.to_header_api_auth()) def get_cluster(self, project_id: str, cluster_id: str) -> Dict: """获取集群信息""" url = self._config.get_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json('GET', url) @parse_response_data() def get_project(self, project_id: str) -> Dict: """获取项目信息""" url = self._config.get_project_url.format(project_id=project_id) return self._client.request_json('GET', url)
class ProxyClient(BkApiClient): """访问代理服务的client :param proxy_data: 访问代理需要的内容,包含host、token等 """ def __init__(self, proxy_config: Optional[ProxyConfig] = None): self.proxy_config = proxy_config self.request = proxy_config.request self._client = BaseHttpClient( ProxyAuth(proxy_config.token, proxy_config.content_type)) @property def source_url(self): """获取真实服务的url 去除proxy配置中添加的前缀,获取真正的路径,再添加域名组装到真正路径 """ source_path = self.request.path if self.proxy_config: source_path = self.request.path.split( self.proxy_config.prefix_path)[-1] return urljoin(self.proxy_config.host, source_path) @property def request_params(self): """获取请求的params数据 TODO: 后续可能会有要删除的字段 """ return self.request.query_params @property def request_data(self): """获取请求的data数据""" return self.request.data def proxy(self, **kwargs): return self._client.request_json( self.request.method, self.source_url, params=self.request_params, json=self.request_data, verify=self.proxy_config.verify_ssl, **kwargs, )
class BkCCClient(BkApiClient): """CMDB API SDK""" def __init__(self, username: str, bk_supplier_account: Optional[str] = settings. BKCC_DEFAULT_SUPPLIER_ACCOUNT): self._config = BkCCConfig(host=settings.COMPONENT_HOST) self._client = BaseHttpClient( BkCCAuth(username, bk_supplier_account=bk_supplier_account)) @response_handler(default=dict) def search_business( self, page: PageData, fields: Optional[List] = None, condition: Optional[Dict] = None, bk_supplier_account: Optional[str] = None, ) -> Dict: """ 获取业务信息 :param page: 分页条件 :param fields: 返回的字段 :param condition: 查询条件 :parma bk_supplier_account: 供应商 :return: 返回业务信息,格式:{'count': 1, 'info': [{'id': 1}]} """ url = self._config.search_business_url params = { 'page': asdict(page), 'fields': fields, 'condition': condition, 'bk_supplier_account': bk_supplier_account, } return self._client.request_json("POST", url, json=params) @response_handler(default=list) def search_biz_inst_topo(self, bk_biz_id: int) -> List: """ 获取业务拓扑信息 :param bk_biz_id: 业务 ID :return: 业务拓扑信息 """ url = self._config.search_biz_inst_topo_url params = {'bk_biz_id': bk_biz_id} return self._client.request_json('POST', url, json=params) @response_handler(default=dict) def get_biz_internal_module(self, bk_biz_id: int) -> Dict: """ 查询内部模块拓扑 :param bk_biz_id: 业务 ID :return: 内部模块拓扑信息 """ url = self._config.get_biz_internal_module_url params = {'bk_biz_id': bk_biz_id} return self._client.request_json('POST', url, json=params) @response_handler(default=dict) def list_biz_hosts( self, bk_biz_id: int, page: PageData, bk_set_ids: List, bk_module_ids: List, fields: List, host_property_filter: Optional[Dict] = None, bk_supplier_account: str = None, ) -> Dict: """ 获取业务主机信息 :param bk_biz_id: 业务 ID :param page: 分页配置 :param bk_set_ids: 集群 ID 列表 :param bk_module_ids: 模块 ID 列表 :param fields: 指定的字段信息 :param host_property_filter: 主机属性组合查询条件 :param bk_supplier_account: 供应商 :return: 主机信息,格式:{'count': 1, 'info': [{'bk_host_innerip': '127.0.0.1'}]} """ url = self._config.list_biz_hosts_url params = { 'bk_biz_id': bk_biz_id, 'page': asdict(page), 'bk_set_ids': bk_set_ids, 'bk_module_ids': bk_module_ids, 'fields': fields, 'host_property_filter': host_property_filter, 'bk_supplier_account': bk_supplier_account, } return self._client.request_json('POST', url, json=params)
def __init__(self, auth: ComponentAuth): self._config = PaaSCCConfig(host=BCS_CC_API_PRE_URL) self._client = BaseHttpClient(auth.to_header_api_auth())
class PaaSCCClient(BkApiClient): """访问 PaaSCC 服务的 Client 对象 :param auth: 包含校验信息的对象 """ def __init__(self, auth: ComponentAuth): self._config = PaaSCCConfig(host=BCS_CC_API_PRE_URL) self._client = BaseHttpClient(auth.to_header_api_auth()) def get_cluster(self, project_id: str, cluster_id: str) -> Dict: """获取集群信息""" if cluster_id in [ cluster["cluster_id"] for cluster in get_shared_clusters() ]: url = self._config.get_cluster_by_id_url.format( cluster_id=cluster_id) return self._client.request_json('GET', url) url = self._config.get_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json('GET', url) @response_handler() def get_cluster_by_id(self, cluster_id: str) -> Dict: """根据集群ID获取集群信息""" url = self._config.get_cluster_by_id_url.format(cluster_id=cluster_id) return self._client.request_json('GET', url) @response_handler() def list_clusters(self, cluster_ids: List[str]) -> Dict: """根据集群ID列表批量获取集群信息""" url = self._config.list_clusters_url data = {"cluster_ids": cluster_ids} # ugly: search_project 的设置才能使接口生效 return self._client.request_json('POST', url, params={'search_project': 1}, json=data) @parse_response_data() def get_project(self, project_id: str) -> Dict: """获取项目信息""" url = self._config.get_project_url.format(project_id=project_id) return self._client.request_json('GET', url) @response_handler() def update_cluster(self, project_id: str, cluster_id: str, data: Dict) -> Dict: """更新集群信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param data: 更新的集群属性,包含status,名称、描述等 """ url = self._config.update_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("PUT", url, json=data) @response_handler() def delete_cluster(self, project_id: str, cluster_id: str): """删除集群 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID """ url = self._config.delete_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("DELETE", url) @response_handler() def update_node_list(self, project_id: str, cluster_id: str, nodes: List[UpdateNodesData]) -> List: """更新节点信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param nodes: 更新的节点属性,包含IP和状态 :returns: 返回更新的节点信息 """ url = self._config.update_node_list_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json( "PATCH", url, json={"updates": [asdict(node) for node in nodes]}) @response_handler() def get_cluster_namespace_list( self, project_id: str, cluster_id: str, limit=None, offset=None, with_lb: Union[bool, int] = False, desire_all_data: Union[bool, int] = None, ) -> Dict[str, Union[int, List[Dict]]]: """获取集群下命名空间列表 :param project_id: 项目ID :param cluster_id: 集群ID :param limit: 每页的数量 :param offset: 第几页 :param with_lb: 是否返回lb,兼容了bool和int型 :param desire_all_data: 是否拉取集群下全量命名空间,兼容bool和int型 :returns: 返回集群下的命名空间 """ url = self._config.get_cluster_namespace_list_url.format( project_id=project_id, cluster_id=cluster_id) req_params = {"desire_all_data": desire_all_data} # NOTE: 根据上层调用,希望获取的是集群下的所有命名空间,因此,当desire_all_data为None时,设置为拉取全量 if desire_all_data is None: req_params["desire_all_data"] = 1 if limit: req_params["limit"] = limit if offset: req_params["offset"] = offset if with_lb: req_params["with_lb"] = with_lb return self._client.request_json("GET", url, params=req_params) @response_handler() def get_node_list(self, project_id: str, cluster_id: str, params: Dict = None) -> Dict: """获取节点列表 :param project_id: 项目ID :param cluster_id: 集群ID :param params: 额外的参数 :returns: 返回节点列表,格式: {"count": 1, "results": [node的详情信息]} """ url = self._config.get_node_list_url.format(project_id=project_id) req_params = {"cluster_id": cluster_id} if params and isinstance(params, dict): req_params.update(params) # 默认拉取项目或集群下的所有节点,防止返回分页数据,导致数据不准确 req_params.setdefault("desire_all_data", 1) return self._client.request_json("GET", url, params=req_params) @response_handler() def list_projects_by_ids(self, project_ids: List[str]) -> Dict: """获取项目列表 :param project_ids: 查询项目的 project_id 列表 """ return self._client.request_json("POST", self._config.list_projects_by_ids, json={'project_ids': project_ids}) @response_handler() def list_namespaces_in_shared_cluster(self, cluster_id: str) -> Dict: url = self._config.list_namespaces_in_shared_cluster.format( cluster_id=cluster_id) # TODO 支持分页查询 return self._client.request_json("GET", url, params={ 'offset': 0, 'limit': 1000 }) @response_handler(default=[]) def list_all_projects(self) -> Dict: return self._client.request_json('GET', url=self._config.list_projects, params={'desire_all_data': 1})
def test_request_json(self, requests_mock): requests_mock.get('http://test.com', json={'foo': 'bar'}) client = BaseHttpClient() assert client.request_json('GET', 'http://test.com/') == {'foo': 'bar'}
def __init__(self, proxy_config: Optional[ProxyConfig] = None): self.proxy_config = proxy_config self.request = proxy_config.request self._client = BaseHttpClient( ProxyAuth(proxy_config.token, proxy_config.content_type))
def __init__(self): self._config = SopsConfig(host=settings.SOPS_API_HOST) self._client = BaseHttpClient(SopsAuth())
class BkRepoClient(BkApiClient): """访问注册到apigw的 Api""" PROJECT_EXIST_CODE = 251005 # 项目已经存在 REPO_EXIST_CODE = 251007 # 仓库已经存在 def __init__(self, username: str, access_token: str = None, password: str = None): self._config = BkRepoApigwConfig() self._bk_repo_raw_config = BkRepoRawConfig() self._client = BaseHttpClient( BkRepoAuth(access_token, username, password), ) def create_project(self, project_code: str, project_name: str, description: str) -> Dict: """创建仓库所属项目 :param project_code: BCS项目code :param project_name: BCS项目名称 :param description: BCS项目描述 :returns: 返回项目 """ data = { "name": project_code, "displayName": project_name, "description": description } resp = self._client.request_json("POST", self._config.create_project, json=data, raise_for_status=False) if resp.get("code") not in [ ErrorCode.NoError, self.PROJECT_EXIST_CODE ]: raise BkRepoCreateProjectError( f"create project error, {resp.get('message')}") return resp def create_chart_repo(self, project_code: str) -> Dict: """创建chart 仓库 :param project_code: BCS项目code :returns: 返回仓库 """ data = { "projectId": project_code, "name": project_code, "type": "HELM", "category": "LOCAL", "public": False, # 容器服务项目自己的仓库 "configuration": { "type": "local" }, } resp = self._client.request_json("POST", self._config.create_chart_repo, json=data, raise_for_status=False) if resp.get("code") not in [ErrorCode.NoError, self.REPO_EXIST_CODE]: raise BkRepoCreateRepoError( f"create repo error, {resp.get('message')}") return resp @response_handler() def set_auth(self, project_code: str, repo_admin_user: str, repo_admin_pwd: str) -> bool: """设置权限 :param project_code: BCS项目code :param repo_admin_user: 仓库admin用户 :param repo_admin_pwd: 仓库admin密码 :returns: 返回auth信息 """ data = { "admin": False, "name": repo_admin_user, "pwd": repo_admin_pwd, "userId": repo_admin_user, "asstUsers": [repo_admin_user], "group": True, "projectId": project_code, } return self._client.request_json("POST", self._config.set_user_auth, json=data, raise_for_status=False) def list_charts(self, project_name: str, repo_name: str, start_time: str = None) -> Dict: """获取项目下的chart :param project_name: 项目名称 :param repo_name: 仓库名称 :param start_time: 增量查询的起始时间 :returns: 返回项目下的chart列表 """ url = self._bk_repo_raw_config.list_charts.format( project_name=project_name, repo_name=repo_name) return self._client.request_json("GET", url, params={"startTime": start_time}) def get_chart_versions(self, project_name: str, repo_name: str, chart_name: str) -> List: """获取项目下指定chart的版本列表 :param project_name: 项目名称 :param repo_name: 仓库名称 :param chart_name: chart 名称 :returns: 返回chart版本列表 """ url = self._bk_repo_raw_config.get_chart_versions.format( project_name=project_name, repo_name=repo_name, chart_name=chart_name) return self._client.request_json("GET", url) def get_chart_version_detail(self, project_name: str, repo_name: str, chart_name: str, version: str) -> Dict: """获取指定chart版本的详情 :param project_name: 项目名称 :param repo_name: 仓库名称 :param chart_name: chart 名称 :param version: chart 版本 :returns: 返回chart版本详情,包含名称、创建时间、版本、url等 """ url = self._bk_repo_raw_config.get_chart_version_detail.format( project_name=project_name, repo_name=repo_name, chart_name=chart_name, version=version) return self._client.request_json("GET", url) def delete_chart_version(self, project_name: str, repo_name: str, chart_name: str, version: str) -> Dict: """删除chart版本 :param project_name: 项目名称 :param repo_name: 仓库名称 :param chart_name: chart 名称 :param version: chart 版本 :returns: 返回删除信息,格式: {"deleted": True} """ url = self._bk_repo_raw_config.delete_chart_version.format( project_name=project_name, repo_name=repo_name, chart_name=chart_name, version=version) resp = self._client.request_json("DELETE", url) if not (resp.get("deleted") or "no such file or directory" in resp.get("error", "")): raise BkRepoDeleteVersionError( f"delete chart version error, {resp}") return resp
def test_request_error(self, req_exc, exc, requests_mock): requests_mock.get('http://test.com', exc=req_exc) with pytest.raises(exc): BaseHttpClient().request('GET', 'http://test.com/')
class PaaSCCClient(BkApiClient): """访问 PaaSCC 服务的 Client 对象 :param auth: 包含校验信息的对象 """ def __init__(self, auth: ComponentAuth): self._config = PaaSCCConfig(host=BCS_CC_API_PRE_URL) self._client = BaseHttpClient(auth.to_header_api_auth()) def get_cluster(self, project_id: str, cluster_id: str) -> Dict: """获取集群信息""" url = self._config.get_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json('GET', url) @parse_response_data() def get_project(self, project_id: str) -> Dict: """获取项目信息""" url = self._config.get_project_url.format(project_id=project_id) return self._client.request_json('GET', url) @response_handler() def update_cluster(self, project_id: str, cluster_id: str, data: Dict) -> Dict: """更新集群信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param data: 更新的集群属性,包含status,名称、描述等 """ url = self._config.update_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("PUT", url, json=data) @response_handler() def delete_cluster(self, project_id: str, cluster_id: str): """删除集群 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID """ url = self._config.delete_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("DELETE", url) @response_handler() def update_node_list(self, project_id: str, cluster_id: str, nodes: List[UpdateNodesData]) -> List: """更新节点信息 :param project_id: 项目32为长度 ID :param cluster_id: 集群ID :param nodes: 更新的节点属性,包含IP和状态 :returns: 返回更新的节点信息 """ url = self._config.update_node_list_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json("PATCH", url, json={"updates": [asdict(node) for node in nodes]}) @response_handler() def get_cluster_namespace_list( self, project_id: str, cluster_id: str, limit=None, offset=None, with_lb: Union[bool, int] = False, desire_all_data: Union[bool, int] = None, ) -> Dict[str, Union[int, List[Dict]]]: """获取集群下命名空间列表 :param project_id: 项目ID :param cluster_id: 集群ID :param limit: 每页的数量 :param offset: 第几页 :param with_lb: 是否返回lb,兼容了bool和int型 :param desire_all_data: 是否拉取集群下全量命名空间,兼容bool和int型 :returns: 返回集群下的命名空间 """ url = self._config.get_cluster_namespace_list_url.format(project_id=project_id, cluster_id=cluster_id) req_params = {"desire_all_data": desire_all_data} # NOTE: 根据上层调用,希望获取的是集群下的所有命名空间,因此,当desire_all_data为None时,设置为拉取全量 if desire_all_data is None: req_params["desire_all_data"] = 1 if limit: req_params["limit"] = limit if offset: req_params["offset"] = offset if with_lb: req_params["with_lb"] = with_lb return self._client.request_json("GET", url, params=req_params)