1818to the current version of the project delivered to anyone in the future.
1919"""
2020from enum import Enum
21- from typing import Type
21+ from typing import Dict , Optional , Type
2222
2323import jsonschema
2424from django .utils .translation import ugettext_lazy as _
25+ from pydantic import BaseModel , constr
2526from pytimeparse import parse
2627
2728from bkflow .exceptions import ValidationError
2829from bkflow .plugin .space_plugin_config_parser import SpacePluginConfigParser
2930from bkflow .utils .apigw import check_url_from_apigw
3031
32+ valid_api_key = constr (regex = r"^[A-Za-z0-9_]+$" )
33+
3134
3235class 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
204208class 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
241256class 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
0 commit comments