Skip to content

Commit

Permalink
优化请求 bk-apigateway 接口失败时,打印的错误消息 (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-smile authored Nov 28, 2023
1 parent c356392 commit 7c0130c
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 47 deletions.
5 changes: 5 additions & 0 deletions sdks/apigw-manager/CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Change logs

### 2.0.2
- 优化请求 bk-apigateway 接口失败时,打印的错误消息
- 添加指令 add_related_apps,支持为网关添加关联应用
- definition.yaml 添加 spec_version 字段,指定配置文件版本号

### 2.0.1
- 修复镜像 sync-apigateway 中,同步任务失败时,脚本退出码为 0 的问题

Expand Down
9 changes: 9 additions & 0 deletions sdks/apigw-manager/definition.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# definition.yaml 配置文件版本号,必填,固定值 1
spec_version: 1

release:
# 发布版本号,
# 资源配置更新,需更新此版本号才会发布资源版本,此版本号和 sdk 版本号一致,错误设置会影响调用方使用
Expand Down Expand Up @@ -47,11 +50,17 @@ stage:
# 主动授权,网关主动给应用,添加访问网关所有资源的权限
grant_permissions:
- bk_app_code: "{{ settings.BK_APP_CODE }}"
# 按网关授权,包括网关下所有资源,包括未来新创建的资源
grant_dimension: "gateway"

# 应用申请指定网关所有资源的权限,待网关管理员审批后,应用才可访问网关资源
apply_permissions:
- api_name: "{{ settings.BK_APIGW_NAME }}"

# 为网关添加关联应用,关联应用可以通过网关 bk-apigateway 的接口操作网关数据;每个网关最多可有 10 个关联应用
related_apps:
- "{{ settings.BK_APP_CODE }}"

# 网关资源,建议资源配置采用单独的配置文件,可通过网关管理端导出资源配置
resources:
swagger: "2.0"
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 = "2.0.1"
version = "2.0.2"
description = "The SDK for managing blueking gateway resource."
readme = "README.md"
authors = ["blueking <[email protected]>"]
Expand Down
27 changes: 23 additions & 4 deletions sdks/apigw-manager/src/apigw_manager/apigw/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
* specific language governing permissions and limitations under the License.
"""
import os
import sys
import typing

from django.conf import settings
from django.core.management.base import BaseCommand

from apigw_manager.apigw.helper import Definition
from apigw_manager.apigw.utils import get_configuration, parse_value_list
from apigw_manager.core.exceptions import ApiResponseError
from apigw_manager.core.fetch import Fetcher
from apigw_manager.core.permission import Manager as PermissionManager
from apigw_manager.core.sync import Synchronizer
Expand All @@ -32,6 +34,19 @@ def add_arguments(self, parser):
def get_configuration(self, **kwargs):
return get_configuration(**{k: v for k, v in kwargs.items() if v is not None})

def handle(self, *args, **kwargs):
configuration = self.get_configuration(**kwargs)
manager = self.manager_class(configuration)

try:
self.do(manager=manager, configuration=configuration, *args, **kwargs)
except ApiResponseError as err:
self.stderr.write(str(err))
sys.exit(1)

def do(self, manager, configuration, *args, **kwargs):
pass


class DefinitionCommand(ApiCommand):
default_namespace = ""
Expand Down Expand Up @@ -67,16 +82,20 @@ def get_definition(self, define, file, namespace, **kwargs):

return definition.get(namespace, {})

def do(self, manager, definition, *args, **kwargs):
pass

def handle(self, *args, **kwargs):
configuration = self.get_configuration(**kwargs)

manager = self.manager_class(configuration)
definition = self.get_definition(**kwargs)

self.do(manager=manager, definition=definition, configuration=configuration, *args, **kwargs)
try:
self.do(manager=manager, definition=definition, configuration=configuration, *args, **kwargs)
except ApiResponseError as err:
self.stderr.write(str(err))
sys.exit(1)

def do(self, manager, definition, *args, **kwargs):
pass


class FetchCommand(ApiCommand):
Expand Down
17 changes: 16 additions & 1 deletion sdks/apigw-manager/src/apigw_manager/apigw/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
class Definition:
"""Gateway model definitions"""

valid_spec_versions = ["1"]

@classmethod
def load_from(cls, path, dictionary):
with open(path) as fp:
Expand All @@ -41,7 +43,20 @@ def load(cls, definition, dictionary):
return cls(rendered)

def __init__(self, definition):
self.loaded = yaml_load(definition)
loaded = yaml_load(definition)

self._check_spec_version(loaded)

self.loaded = loaded

def _check_spec_version(self, definition):
spec_version = definition.get("spec_version")
if not spec_version:
logger.warning("please add `spec_version: 1` to definition.yaml")
return

if str(spec_version) not in self.valid_spec_versions:
raise ValueError("spec_version configured in definition.yaml is wrong, choices: 1")

def _get_namespace_list(self, namespace):
if not namespace:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-蓝鲸 PaaS 平台(BlueKing-PaaS) available.
* Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
"""
from apigw_manager.apigw.command import SyncCommand
from apigw_manager.core.exceptions import ApiResponseError


class Command(SyncCommand):
"""Add related apps for gateway"""

default_namespace = "related_apps"

def do(self, manager, definition, *args, **kwargs):
if not definition:
print("no related apps found, skip")
return

try:
manager.add_related_apps(target_app_codes=definition)
except ApiResponseError as err:
print("warning!! Add related apps error, %s" % str(err))
return

print(
"Add related apps for gateway %s: %s"
% (
manager.config.api_name,
", ".join(definition)
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
"""
import sys

from apigw_manager.apigw import helper
from apigw_manager.apigw.command import FetchCommand

Expand All @@ -25,9 +27,7 @@ def add_arguments(self, parser):
parser.add_argument("--bk-app-secret", dest="bk_app_secret", help="app secret")
parser.add_argument("--no-save", default=False, action="store_true", help="do not save the public key")

def handle(self, print_, no_save, *args, **kwargs):
configuration = self.get_configuration(**kwargs)
manager = self.manager_class(configuration)
def do(self, manager, configuration, print_, no_save, *args, **kwargs):
result = manager.public_key()

if print_:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ def do(self, manager, definition, *args, **kwargs):

for permission in definition:
permission.setdefault("target_app_code", permission.pop("bk_app_code", None))
permission.setdefault("grant_dimension", "api")

manager.grant_permission(**permission)
grant_dimension = permission.pop("grant_dimension", "api")
if grant_dimension == "gateway":
grant_dimension = "api"

manager.grant_permission(grant_dimension=grant_dimension, **permission)

print(
"Granted API gateway %s permission for app code %s, dimension %s"
"Granted gateway %s permission for app code %s, dimension %s"
% (
manager.config.api_name,
permission["target_app_code"],
permission["grant_dimension"],
grant_dimension,
)
)
16 changes: 7 additions & 9 deletions sdks/apigw-manager/src/apigw_manager/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,18 @@ def __str__(self):
return self.operation_id


class ApiResultError(Exception):
def __init__(self, code, message):
self.code = code
class ApiResponseError(Exception):
"""There was an exception that occurred while handling the response"""
def __init__(self, message):
self.message = message

def __str__(self):
return "code: %s, %s" % (self.code, self.message)
return self.message


class ApiRequestError(Exception):
def __init__(self, status_code, code, message):
self.status_code = status_code
class ApiResultError(ApiResponseError):
def __init__(self, code, message):
self.code = code
self.message = message

def __str__(self):
return "status_code: %s, code: %s, %s" % (self.status_code, self.code, self.message)
return "code: %s, %s" % (self.code, self.message)
25 changes: 5 additions & 20 deletions sdks/apigw-manager/src/apigw_manager/core/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from bkapi.bk_apigateway.client import Client as BKAPIGatewayClient
from future.utils import raise_from

from apigw_manager.core.exceptions import ApiException, ApiRequestError, ApiResultError
from bkapi_client_core.exceptions import HTTPResponseError
from apigw_manager.core.exceptions import ApiException, ApiResponseError, ApiResultError
from bkapi_client_core.exceptions import ResponseError

if typing.TYPE_CHECKING:
from apigw_manager.core import configuration
Expand Down Expand Up @@ -92,8 +92,9 @@ def _call(self, operation, files=None, **kwargs):

try:
return operation(**data)
except HTTPResponseError as err:
raise_from(self._convert_operation_exception(err), err)
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_from(ApiException(operation_id), err)

Expand All @@ -107,19 +108,3 @@ def _parse_result(self, result, convertor, code=0):
)

return convertor(result)

def _convert_operation_exception(self, err: HTTPResponseError):
response = err.response
if response is None or response.status_code / 100 != 4:
return err

try:
result = response.json()
except Exception:
return err

return ApiRequestError(
response.status_code,
result.get("code"),
result.get("message"),
)
4 changes: 4 additions & 0 deletions sdks/apigw-manager/src/apigw_manager/core/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ def sync_resources_config(self, content, *args, **kwargs):
def sync_resource_docs_by_archive(self, *args, **kwargs):
result = self._call(self.client.api.import_resource_docs_by_archive, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))

def add_related_apps(self, *args, **kwargs):
result = self._call(self.client.api.add_related_apps, *args, **kwargs)
return self._parse_result(result, itemgetter("data"))
10 changes: 5 additions & 5 deletions sdks/apigw-manager/tests/apigw_manager/core/test_hander.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from faker import Faker
from pytest import fixture, raises

from apigw_manager.core.exceptions import ApiRequestError
from apigw_manager.core.exceptions import ApiResponseError
from apigw_manager.core.handler import Handler
from bkapi_client_core.exceptions import HTTPResponseError

Expand Down Expand Up @@ -90,12 +90,12 @@ def test_call_with_cache(self, handler: Handler, operation, operation_id, faker,

def test_call_connect_error(self, handler: Handler, operation):
operation.side_effect = HTTPResponseError()
with raises(HTTPResponseError):
with raises(ApiResponseError):
handler._call(operation)

def test_call_server_error(self, handler: Handler, operation, mocker):
operation.side_effect = HTTPResponseError(response=mocker.MagicMock(status_code=500))
with raises(HTTPResponseError):
with raises(ApiResponseError):
handler._call(operation)

def test_call_request_error(self, handler: Handler, operation, mocker):
Expand All @@ -106,7 +106,7 @@ def test_call_request_error(self, handler: Handler, operation, mocker):
)
)

with raises(ApiRequestError):
with raises(ApiResponseError):
handler._call(operation)

def test_call_request_error_with_no_json(self, handler: Handler, operation, mocker):
Expand All @@ -117,5 +117,5 @@ def test_call_request_error_with_no_json(self, handler: Handler, operation, mock
)
)

with raises(HTTPResponseError):
with raises(ApiResponseError):
handler._call(operation)

0 comments on commit 7c0130c

Please sign in to comment.