Skip to content

Commit 01f9af6

Browse files
authored
Merge pull request #128 from TencentBlueKing/develop
Merge dev to master for release V1.9.3
2 parents 0fa2909 + 7bb734d commit 01f9af6

File tree

18 files changed

+257
-74
lines changed

18 files changed

+257
-74
lines changed

app_desc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
spec_version: 2
2-
app_version: "1.9.2"
2+
app_version: "1.9.3"
33
app:
44
region: default
55
bk_app_code: &APP_CODE bk_flow_engine

bkflow/pipeline_plugins/components/collections/uniform_api/v2_0_0.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
)
3535
from bkflow.pipeline_plugins.query.uniform_api.utils import UniformAPIClient
3636
from bkflow.pipeline_plugins.utils import convert_dict_value
37+
from bkflow.space.configs import UniformAPIConfigHandler
3738
from bkflow.utils.api_client import HttpRequestResult
3839
from bkflow.utils.handlers import handle_plain_log
3940

@@ -122,7 +123,6 @@ def _dispatch_schedule_trigger(self, data, parent_data, callback_data=None):
122123
callback = api_data.pop("uniform_api_plugin_callback", None)
123124
method = api_data.pop("uniform_api_plugin_method")
124125
resp_data_path: str = api_data.pop("response_data_path", None)
125-
126126
# 获取空间相关配置信息
127127
interface_client = InterfaceModuleClient()
128128
space_infos_result = interface_client.get_space_infos(
@@ -137,12 +137,18 @@ def _dispatch_schedule_trigger(self, data, parent_data, callback_data=None):
137137
return False
138138

139139
space_configs = space_infos_result.get("data", {}).get("configs", {})
140+
uniform_api_config = space_configs.get("uniform_api", {})
141+
validated_config = UniformAPIConfigHandler(uniform_api_config).handle()
142+
if validated_config.exclude_none_fields:
143+
# 过滤字符串为空的基础类型
144+
keys_to_remove = [key for key, value in api_data.items() if value == ""]
145+
self.logger.info(f"none fields keys to remove: {keys_to_remove}")
146+
for key in keys_to_remove:
147+
api_data.pop(key)
148+
self.logger.info(f"plugin_data after poping: {api_data}")
140149

141150
# 开启的enable_api_parameter_conversion配置只对POST参数生效
142-
if (
143-
space_configs.get("uniform_api", {}).get("enable_api_parameter_conversion", False)
144-
and method.upper() == "POST"
145-
):
151+
if validated_config.enable_api_parameter_conversion:
146152
# 启动参数转换
147153
api_data = convert_dict_value(api_data)
148154

bkflow/pipeline_plugins/query/uniform_api/uniform_api.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from bkflow.exceptions import APIResponseError, ValidationError
2525
from bkflow.pipeline_plugins.query.uniform_api.utils import UniformAPIClient
2626
from bkflow.pipeline_plugins.query.utils import query_response_handler
27-
from bkflow.space.configs import UniformApiConfig
27+
from bkflow.space.configs import UniformApiConfig, UniformAPIConfigHandler
2828
from bkflow.space.models import SpaceConfig
2929
from bkflow.utils.api_client import HttpRequestResult
3030

@@ -33,6 +33,7 @@ class UniformAPICategorySerializer(serializers.Serializer):
3333
scope_type = serializers.CharField(required=False)
3434
scope_value = serializers.CharField(required=False)
3535
key = serializers.CharField(required=False)
36+
api_name = serializers.CharField(required=False)
3637

3738

3839
class UniformAPIListSerializer(serializers.Serializer):
@@ -42,6 +43,7 @@ class UniformAPIListSerializer(serializers.Serializer):
4243
scope_value = serializers.CharField(required=False)
4344
category = serializers.CharField(required=False)
4445
key = serializers.CharField(required=False)
46+
api_name = serializers.CharField(required=False)
4547

4648

4749
class UniformAPIMetaSerializer(serializers.Serializer):
@@ -52,10 +54,15 @@ class UniformAPIMetaSerializer(serializers.Serializer):
5254

5355
def _get_space_uniform_api_list_info(space_id, request_data, config_key):
5456
uniform_api_config = SpaceConfig.get_config(space_id=space_id, config_name=UniformApiConfig.name)
55-
if not uniform_api_config.get(config_key):
57+
if not uniform_api_config:
5658
raise ValidationError("接入平台未注册统一API, 请联系对应接入平台管理员")
5759
client = UniformAPIClient()
58-
url = uniform_api_config[config_key]
60+
uniform_api_config = UniformAPIConfigHandler(uniform_api_config).handle()
61+
# 弹出此参数避免透传
62+
api_name = request_data.pop("api_name", UniformApiConfig.Keys.DEFAULT_API_KEY.value)
63+
url = uniform_api_config.api.get(api_name, {}).get(config_key)
64+
if not url:
65+
raise ValidationError("对应API未配置, 请联系对应接入平台管理员")
5966
request_result: HttpRequestResult = client.request(url=url, method="GET", data=request_data)
6067
if not request_result.result:
6168
raise APIResponseError(f"请求统一API列表失败: {request_result.message}")

bkflow/space/configs.py

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@
1818
to the current version of the project delivered to anyone in the future.
1919
"""
2020
from enum import Enum
21-
from typing import Type
21+
from typing import Dict, Optional, Type
2222

2323
import jsonschema
2424
from django.utils.translation import ugettext_lazy as _
25+
from pydantic import BaseModel, constr
2526
from pytimeparse import parse
2627

2728
from bkflow.exceptions import ValidationError
2829
from bkflow.plugin.space_plugin_config_parser import SpacePluginConfigParser
2930
from bkflow.utils.apigw import check_url_from_apigw
3031

32+
valid_api_key = constr(regex=r"^[A-Za-z0-9_]+$")
33+
3134

3235
class SpaceConfigValueType(Enum):
3336
# json 类型
@@ -98,6 +101,7 @@ def get_config(cls, name):
98101

99102
@classmethod
100103
def get_all_configs(cls, only_public=False):
104+
101105
# copy, 降低被修改风险
102106
if only_public:
103107
return {name: config_cls for name, config_cls in cls.__hub.items() if config_cls.is_public}
@@ -203,32 +207,33 @@ def validate(cls, value: dict):
203207

204208
class UniformApiConfig(BaseSpaceConfig):
205209
name = "uniform_api"
206-
desc = _("是否开启统一API")
207210
value_type = SpaceConfigValueType.JSON.value
208211
default_value = {}
209-
example = {"meta_apis": "{meta_apis url}", "api_categories": "{api_categories url}"}
212+
example = {
213+
"api": {
214+
"{api_key}": {
215+
"meta_apis": "{meta_apis url}",
216+
"api_categories": "{api_categories url}",
217+
"display_name": "{display_name}",
218+
}
219+
}
220+
}
221+
desc = _("API 插件配置 (如更改配置,可能对已存在数据产生不兼容影响,请谨慎操作)")
222+
"""
223+
仍然支持读取 旧 SCHEMA 但不能支持继续配置
224+
旧 SCHEMA 格式 example = {"meta_apis": "{meta_apis url}", "api_categories": "{api_categories url}"}
225+
"""
210226

211227
class Keys(Enum):
212228
META_APIS = "meta_apis"
213229
API_CATEGORIES = "api_categories"
214-
215-
SCHEMA = {
216-
"type": "object",
217-
"required": ["meta_apis"],
218-
"properties": {
219-
Keys.META_APIS.value: {"type": "string"},
220-
Keys.API_CATEGORIES.value: {"type": "string"},
221-
},
222-
}
230+
DISPLAY_NAME = "display_name"
231+
DEFAULT_DISPLAY_NAME = "API插件"
232+
DEFAULT_API_KEY = "default"
223233

224234
@classmethod
225-
def validate(cls, value: dict):
226-
try:
227-
jsonschema.validate(value, cls.SCHEMA)
228-
except jsonschema.ValidationError as e:
229-
raise ValidationError(f"[validate uniform api config error]: {str(e)}")
230-
231-
meta_apis_from_apigw = check_url_from_apigw(value[cls.Keys.META_APIS.value])
235+
def check_url(cls, value):
236+
meta_apis_from_apigw = check_url_from_apigw(value.get(cls.Keys.META_APIS.value))
232237
category_config = value.get(cls.Keys.API_CATEGORIES.value)
233238
api_categories_from_apigw = check_url_from_apigw(category_config) if category_config else True
234239
if not (api_categories_from_apigw and meta_apis_from_apigw):
@@ -237,6 +242,16 @@ def validate(cls, value: dict):
237242
)
238243
return True
239244

245+
@classmethod
246+
def validate(cls, value: dict):
247+
try:
248+
model = SchemaV2Model(**value)
249+
except ValueError as e:
250+
raise ValidationError(f"[validate uniform api config error]: {str(e)} should have {str(cls.example)}")
251+
for obj in model.api.values():
252+
cls.check_url(obj)
253+
return True
254+
240255

241256
class SuperusersConfig(BaseSpaceConfig):
242257
name = "superusers"
@@ -297,3 +312,69 @@ class SpacePluginConfig(BaseSpaceConfig):
297312
@classmethod
298313
def validate(cls, value: dict):
299314
return SpacePluginConfigParser(config=value).is_valid()
315+
316+
317+
# 定义 SCHEMA_V1 对应的模型
318+
class SchemaV1Model(BaseModel):
319+
meta_apis: str
320+
api_categories: Optional[str] = None
321+
322+
323+
# 定义 SCHEMA 对应的模型
324+
class ApiModel(BaseModel):
325+
meta_apis: str
326+
api_categories: str
327+
display_name: str
328+
329+
def get(self, field_name, default=None):
330+
# 由于获取插件种类/列表时候传入的 key 不确定 需要提供一个 get 方法
331+
return getattr(self, field_name, default)
332+
333+
334+
class CommonModel(BaseModel):
335+
exclude_none_fields: Optional[str] = None
336+
enable_api_parameter_conversion: Optional[str] = None
337+
338+
339+
class SchemaV2Model(BaseModel):
340+
api: Dict[valid_api_key, ApiModel]
341+
common: Optional[CommonModel] = None
342+
343+
def __getattr__(self, key):
344+
try:
345+
super().__getattribute__(key)
346+
except AttributeError:
347+
# 当前没有则从 common 中获取 如果出现不存在的字段则报错
348+
if key not in CommonModel.__fields__:
349+
raise
350+
if self.common and hasattr(self.common, key):
351+
return getattr(self.common, key)
352+
return None
353+
354+
355+
class UniformAPIConfigHandler:
356+
def __init__(self, config: dict):
357+
self.config = config
358+
359+
def handle(self):
360+
model = None
361+
try:
362+
# 尝试按新协议解析
363+
model = SchemaV2Model(**self.config)
364+
return model
365+
except ValueError:
366+
pass
367+
try:
368+
# 兼容旧协议解析
369+
v1_model = SchemaV1Model(**self.config)
370+
except ValueError as e:
371+
raise ValidationError(
372+
f"[validate uniform api config error]: {str(e)} should have {UniformApiConfig.example}"
373+
)
374+
api_model = ApiModel(
375+
meta_apis=v1_model.meta_apis,
376+
api_categories=v1_model.api_categories,
377+
display_name=UniformApiConfig.Keys.DEFAULT_DISPLAY_NAME.value,
378+
)
379+
model = SchemaV2Model(api={UniformApiConfig.Keys.DEFAULT_API_KEY.value: api_model})
380+
return model

bkflow/space/views.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,7 @@ def get_space_infos(self, request, *args, **kwargs):
196196
space_id=data["space_id"], name=api_gateway_credential_name, type=CredentialType.BK_APP.value
197197
).value
198198
except (Credential.DoesNotExist, SpaceConfigDefaultValueNotExists) as e:
199-
logger.exception(
200-
"CredentialViewSet 获取空间下的凭证异常, space_id={}, err={}, ".format(data["space_id"], e)
201-
)
199+
logger.exception("CredentialViewSet 获取空间下的凭证异常, space_id={}, err={}, ".format(data["space_id"], e))
202200
value = {}
203201
else:
204202
value = SpaceConfig.get_config(space_id=data["space_id"], config_name=config_name)
@@ -229,13 +227,16 @@ def list(self, request, *args, **kwargs):
229227
raise PermissionDenied()
230228
return super().list(request, *args, **kwargs)
231229

232-
@swagger_auto_schema(
233-
method="get", operation_summary="获取所有空间配置元信息", query_serializer=SpaceConfigBaseQuerySerializer
234-
)
230+
def process_config(self, config_dict):
231+
if not config_dict.get("default_value"):
232+
config_dict["default_value"] = None
233+
return config_dict
234+
235+
@swagger_auto_schema(method="get", operation_summary="获取所有空间配置元信息", query_serializer=SpaceConfigBaseQuerySerializer)
235236
@action(detail=False, methods=["GET"])
236237
def config_meta(self, request, *args, **kwargs):
237238
configs = SpaceConfigHandler.get_all_configs()
238-
return Response({name: config.to_dict() for name, config in configs.items()})
239+
return Response({name: self.process_config(config.to_dict()) for name, config in configs.items()})
239240

240241
@swagger_auto_schema(
241242
method="post",
@@ -250,9 +251,7 @@ def batch_apply(self, request, *args, **kwargs):
250251
SpaceConfig.objects.batch_update(space_id=space_id, configs=configs)
251252
return Response(SpaceConfig.objects.get_space_config_info(space_id=space_id, simplified=False))
252253

253-
@swagger_auto_schema(
254-
method="get", operation_summary="获取空间下所有配置", query_serializer=SpaceConfigBaseQuerySerializer
255-
)
254+
@swagger_auto_schema(method="get", operation_summary="获取空间下所有配置", query_serializer=SpaceConfigBaseQuerySerializer)
256255
@action(detail=False, methods=["GET"])
257256
def get_all_space_configs(self, request, *args, **kwargs):
258257
ser = SpaceConfigBaseQuerySerializer(data=request.query_params)

bkflow/task/node_log.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def fetch_node_logs(self, node_id, version_id, *args, **kwargs):
5959
if self.private_token:
6060
url_params.update({"private_token": self.private_token})
6161
url = self.url.rstrip("/") + f"/?{urlencode(url_params)}"
62-
payload = {"query": {"query_string": f"json.node_id:{node_id} AND json.version:{version_id}"}}
62+
payload = {"query": {"query_string": f"__ext_json.node_id:{node_id} AND __ext_json.version:{version_id}"}}
6363
response = requests.get(url, headers=self.headers, data=json.dumps(payload))
6464
logger.info(
6565
f"[PaaS3NodeLogDataSource fetch_node_logs] request {url} with payload {payload} and "

bkflow/template/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
TemplateMockDataViewSet,
2727
TemplateMockSchemeViewSet,
2828
TemplateViewSet,
29+
TemplateMockTaskViewSet,
2930
)
3031
from bkflow.template.views.variable import VariableViewSet
3132

@@ -34,6 +35,7 @@
3435
router.register(r"^admin", AdminTemplateViewSet, basename="admin_template")
3536
router.register(r"^template_mock_data", TemplateMockDataViewSet, basename="template_mock_data")
3637
router.register(r"^template_mock_scheme", TemplateMockSchemeViewSet, basename="template_mock_scheme")
38+
router.register(r"^template_mock_task", TemplateMockTaskViewSet, basename="template_mock_task")
3739
router.register(r"", TemplateViewSet, basename="template")
3840

3941
urlpatterns = [

bkflow/template/views/template.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from rest_framework import mixins
2727
from rest_framework.decorators import action
2828
from rest_framework.response import Response
29+
from rest_framework.viewsets import GenericViewSet
2930

3031
from bkflow.apigw.serializers.task import (
3132
CreateMockTaskWithPipelineTreeSerializer,
@@ -40,7 +41,11 @@
4041
from bkflow.pipeline_web.drawing_new.drawing import draw_pipeline as draw_pipeline_tree
4142
from bkflow.pipeline_web.preview import preview_template_tree
4243
from bkflow.pipeline_web.preview_base import PipelineTemplateWebPreviewer
43-
from bkflow.space.configs import GatewayExpressionConfig
44+
from bkflow.space.configs import (
45+
GatewayExpressionConfig,
46+
UniformApiConfig,
47+
UniformAPIConfigHandler,
48+
)
4449
from bkflow.space.exceptions import SpaceConfigDefaultValueNotExists
4550
from bkflow.space.models import SpaceConfig
4651
from bkflow.space.permissions import SpaceSuperuserPermission
@@ -69,6 +74,7 @@
6974
TemplateMockDataSerializer,
7075
TemplateMockSchemeSerializer,
7176
TemplateOperationRecordSerializer,
77+
TemplateRelatedResourceSerializer,
7278
TemplateSerializer,
7379
)
7480
from bkflow.template.utils import analysis_pipeline_constants_ref
@@ -155,9 +161,7 @@ def create_task(self, request, space_id, *args, **kwargs):
155161
raise APIResponseError(result["message"])
156162
return Response(result["data"])
157163

158-
@swagger_auto_schema(
159-
method="POST", operation_description="流程批量删除", request_body=TemplateBatchDeleteSerializer
160-
)
164+
@swagger_auto_schema(method="POST", operation_description="流程批量删除", request_body=TemplateBatchDeleteSerializer)
161165
@action(methods=["POST"], detail=False, url_path="batch_delete")
162166
def batch_delete(self, request, *args, **kwargs):
163167
ser = TemplateBatchDeleteSerializer(data=request.data)
@@ -255,6 +259,11 @@ def get_space_related_configs(self, request, *args, **kwargs):
255259
except SpaceConfigDefaultValueNotExists as e:
256260
logger.error(f"space_id={template.space_id}, config_name={GatewayExpressionConfig.name}, error={e}")
257261
raise
262+
# 增加 uniform api 的配置项返回
263+
uniform_api_config = SpaceConfig.get_config(space_id=template.space_id, config_name=UniformApiConfig.name)
264+
if uniform_api_config:
265+
uniform_api_config = UniformAPIConfigHandler(uniform_api_config).handle().dict()
266+
data.update({UniformApiConfig.name: uniform_api_config})
258267
return Response(data)
259268

260269
@swagger_auto_schema(methods=["post"], operation_description="流程树预览", request_body=PreviewTaskTreeSerializer)
@@ -419,3 +428,17 @@ def perform_create(self, serializer):
419428
def perform_update(self, serializer):
420429
user = serializer.context.get("request").user
421430
serializer.save(operator=user.username)
431+
432+
433+
class TemplateMockTaskViewSet(mixins.ListModelMixin, GenericViewSet):
434+
DEFAULT_PERMISSION = TemplateRelatedResourcePermission.MOCK_PERMISSION
435+
permission_classes = [AdminPermission | SpaceSuperuserPermission | TemplateRelatedResourcePermission]
436+
437+
@swagger_auto_schema(query_serializer=TemplateRelatedResourceSerializer)
438+
def list(self, request, *args, **kwargs):
439+
ser = TemplateRelatedResourceSerializer(data=request.query_params)
440+
ser.is_valid(raise_exception=True)
441+
space_id, template_id = ser.validated_data["space_id"], ser.validated_data["template_id"]
442+
client = TaskComponentClient(space_id=space_id)
443+
result = client.task_list(data={"template_id": template_id, "space_id": space_id, "create_method": "MOCK"})
444+
return Response(result)

0 commit comments

Comments
 (0)