Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sdks/apigw-manager/CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Change logs

### 5.0.1

- [fix] 修复 v2 API 响应解析逻辑,20x 响应直接返回 `data`,非 20x 响应从 `error` 中解析错误信息
- [fix] 适配 v2 资源版本接口返回结构,资源版本列表使用数组判断是否存在,发布时不再依赖资源版本响应中的 `comment`
- [fix] 修复同步环境 MCP Server 时重复读取 `data` 导致同步结果无法输出的问题
- [fix] `apply_apigw_permissions` 申请权限时补充 v2 接口必填的 `applicant` 字段

### 5.0.0

- [feat] generate_resources_yaml 命令支持合并手动配置的额外资源文件
Expand Down
2 changes: 1 addition & 1 deletion sdks/apigw-manager/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "apigw-manager"
version = "5.0.0"
version = "5.0.1"
description = "The SDK for managing blueking gateway resource."
readme = "README.md"
authors = ["blueking <blueking@tencent.com>"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def do(self, manager, definition, *args, **kwargs):

for permission in definition:
permission.setdefault("target_app_code", manager.config.bk_app_code)
permission.setdefault("applicant", permission["target_app_code"])

# v2 使用 gateway_name 替代 api_name
if "api_name" in permission:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _create_resource_version(self, releaser, version, comment):

def _check_resource_version_exists(self, fetcher, version):
resource_versions = fetcher.list_resource_versions(version=str(version))
return resource_versions["count"] != 0
return bool(resource_versions)

def _generate_sdks(self, releaser, version, *args, **kwargs):
try:
Expand Down Expand Up @@ -132,7 +132,7 @@ def handle(self, stage, title, comment, generate_sdks, no_pub, *args, **kwargs):
if not no_pub:
result = releaser.release(
version=resource_version["version"],
comment=comment or resource_version.get("comment", ""),
comment=comment or "",
stage_names=stage,
)
print(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def get_definition(self, define, file, namespace, **kwargs):
def do(self, manager, definition, *args, **kwargs):
for stage_definition in definition:
result = manager.sync_stage_mcp_servers(**stage_definition)
for mcp_sync_result in result.get("data", []):
for mcp_sync_result in result:
print(
"API gateway stage mcp servers synchronization completed [ id:%s,name:%s,action:%s ]"
% (mcp_sync_result["id"], mcp_sync_result["name"], mcp_sync_result["action"])
Expand Down
7 changes: 3 additions & 4 deletions sdks/apigw-manager/src/apigw_manager/core/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# to the current version of the project delivered to anyone in the future.

from apigw_manager.core.handler import Handler
from apigw_manager.core.utils import itemgetter


class Fetcher(Handler):
Expand All @@ -25,13 +24,13 @@ class Fetcher(Handler):
def public_key(self, *args, **kwargs):
"""Get the API gateway public key according to the name"""
result = self._call_v2_with_cache(self.client.api.v2_sync_get_gateway_public_key_new, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def latest_resource_version(self, *args, **kwargs):
"""Get the latest resource version"""
result = self._call_v2(self.client.api.v2_sync_get_latest_resource_version, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def list_resource_versions(self, *args, **kwargs):
result = self._call_v2(self.client.api.v2_sync_list_resource_versions, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)
72 changes: 22 additions & 50 deletions sdks/apigw-manager/src/apigw_manager/core/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,6 @@ def _put_into_cache(self, operation_id, data, result):

return False

def _call_with_cache(self, operation, **kwargs):
"""Call the API instance, allow data to be retrieved from the cache"""
cache_key = {
"gateway_name": kwargs.get("gateway_name", self.config.gateway_name),
"kwargs": kwargs,
}

operation_id = operation.name
cached, result = self._get_from_cache(operation_id, cache_key)
if cached:
return result

result = self._call(operation, **kwargs)

self._put_into_cache(operation_id, cache_key, result)

return result

def _call_v2_with_cache(self, operation, **kwargs):
"""Call the API instance (v2), allow data to be retrieved from the cache"""
cache_key = {
Expand Down Expand Up @@ -123,31 +105,6 @@ def _get_tenant_id(self):
)
return bk_app_tenant_id

def _call(self, operation, files=None, **kwargs):
"""Call the API instance"""
data = {
"path_params": {"api_name": kwargs.pop("gateway_name", self.config.gateway_name)},
"data": kwargs,
"headers": {
"X-Bkapi-Authorization": kwargs.pop("x_bkapi_authorization", self._get_bkapi_authorization()),
# the header is required by the API gateway plugin bk-tenant-validate, for global tenant app!
# so we set it to system, it would not be used in the gateway
"X-Bk-Tenant-Id": self._get_tenant_id(),
},
"files": files,
}

operation_id = operation.name
logger.debug("call api %s, data: %s", operation_id, data)

try:
return operation(**data)
except ResponseError as err:
message = "%s\n%s\nResponse: %s" % (err, err.curl_command, err.response_text)
raise ApiResponseError(message)
except Exception as err:
raise ApiException(operation_id) from err

def _call_v2(self, operation, files=None, **kwargs):
"""Call the API instance (v2 version):
- Uses "gateway_name" as the key in `path_params` instead of "api_name".
Expand Down Expand Up @@ -180,18 +137,33 @@ def _call_v2(self, operation, files=None, **kwargs):
try:
return operation(**data)
except ResponseError as err:
if err.response_status_code is not None and not self._is_success_status_code(err.response_status_code):
self._raise_v2_result_error(self._get_response_json(err), err.response_status_code, err.response_text)

message = "%s\n%s\nResponse: %s" % (err, err.curl_command, err.response_text)
raise ApiResponseError(message)
except Exception as err:
raise ApiException(operation_id) from err

def _parse_result(self, result, convertor, code=0):
"""Check the code and convert the result"""
logger.debug("code %s, message: %s", result.get("code"), result.get("message"))
if result.get("code") != code:
def _parse_v2_result(self, result):
"""Convert the v2 API response body."""
return result.get("data")

def _raise_v2_result_error(self, result, status_code, response_text):
if isinstance(result, dict) and result.get("error"):
error = result["error"]
raise ApiResultError(
result.get("code"),
result.get("message"),
error.get("code"),
error.get("message"),
)

return convertor(result)
raise ApiResultError(status_code, response_text)

def _get_response_json(self, err):
try:
return err.response_json()
except (TypeError, ValueError):
return None

def _is_success_status_code(self, status_code):
return status_code is not None and 200 <= status_code < 300
5 changes: 2 additions & 3 deletions sdks/apigw-manager/src/apigw_manager/core/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# to the current version of the project delivered to anyone in the future.

from apigw_manager.core.handler import Handler
from apigw_manager.core.utils import itemgetter


class Manager(Handler):
Expand All @@ -25,9 +24,9 @@ class Manager(Handler):
def apply_permission(self, *args, **kwargs):
"""Apply for API Gateway Permissions"""
result = self._call_v2(self.client.api.v2_open_apply_gateway_permission, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def grant_permission(self, *args, **kwargs):
"""Grant API gateway permissions for applications"""
result = self._call_v2(self.client.api.v2_sync_grant_permission, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)
7 changes: 3 additions & 4 deletions sdks/apigw-manager/src/apigw_manager/core/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# to the current version of the project delivered to anyone in the future.

from apigw_manager.core.handler import Handler
from apigw_manager.core.utils import itemgetter


class Releaser(Handler):
Expand All @@ -27,17 +26,17 @@ def create_resource_version(self, *args, **kwargs):
kwargs.pop("title", None)

result = self._call_v2(self.client.api.v2_sync_create_resource_version, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def release(self, *args, **kwargs):
"""release a version"""
kwargs.pop("title", None)

result = self._call_v2(self.client.api.v2_sync_release, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def generate_sdks(self, *args, **kwargs):
"""generate sdks"""

result = self._call_v2(self.client.api.v2_sync_generate_sdk, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)
14 changes: 7 additions & 7 deletions sdks/apigw-manager/src/apigw_manager/core/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,35 @@
import yaml

from apigw_manager.core.handler import Handler
from apigw_manager.core.utils import itemgetter


class Synchronizer(Handler):
"""Synchronous API gateway configuration"""

def sync_basic_config(self, *args, **kwargs):
result = self._call_v2(self.client.api.v2_sync_gateway, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def sync_stage_config(self, *args, **kwargs):
result = self._call_v2(self.client.api.v2_sync_stages, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def sync_stage_mcp_servers(self, *args, **kwargs):
return self._call_v2(self.client.api.v2_sync_stage_mcp_servers, *args, **kwargs)
result = self._call_v2(self.client.api.v2_sync_stage_mcp_servers, *args, **kwargs)
return self._parse_v2_result(result)

def sync_resources_config(self, content, *args, **kwargs):
kwargs["content"] = yaml.dump(dict(content))

result = self._call_v2(self.client.api.v2_sync_resources, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def sync_resource_docs_by_archive(self, *args, **kwargs):
result = self._call_v2(self.client.api.v2_sync_resource_doc, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)

def add_related_apps(self, *args, **kwargs):
kwargs["related_app_codes"] = kwargs.pop("related_apps")

result = self._call_v2(self.client.api.v2_sync_add_related_apps, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
return self._parse_v2_result(result)
9 changes: 0 additions & 9 deletions sdks/apigw-manager/src/apigw_manager/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@
from operator import getitem


def itemgetter(*keys):
"""Chaining value getter of dict"""

def getter(r):
return get_item(r, keys)

return getter


def get_item(r, keys):
"""Get the value according to the keys"""
return reduce(getitem, keys, r)
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,27 @@ def test_do(mock_manager, command, configuration):
[
{"api_name": "test1"},
{"gateway_name": "test2", "grant_dimension": "resource", "resource_names": ["get_user"]},
{"gateway_name": "test3", "grant_dimension": "api"},
{"gateway_name": "test3", "grant_dimension": "api", "applicant": "admin"},
],
)

# v2 使用 gateway_name 替代 api_name,默认按 gateway 维度申请
mock_manager.apply_permission.assert_any_call(
target_app_code=configuration.bk_app_code,
applicant=configuration.bk_app_code,
gateway_name="test1",
grant_dimension="gateway",
)
mock_manager.apply_permission.assert_any_call(
target_app_code=configuration.bk_app_code,
applicant=configuration.bk_app_code,
gateway_name="test2",
grant_dimension="resource",
resource_names=["get_user"],
)
mock_manager.apply_permission.assert_any_call(
target_app_code=configuration.bk_app_code,
applicant="admin",
gateway_name="test3",
grant_dimension="api",
)
Loading
Loading