parent
cdcea7f9d6
commit
b354fe14bf
|
@ -2,6 +2,9 @@ from flask import Blueprint
|
||||||
from flask_restful import Api
|
from flask_restful import Api
|
||||||
|
|
||||||
from controller.asset import views
|
from controller.asset import views
|
||||||
|
from controller.asset import instance
|
||||||
|
from controller.asset import middleware
|
||||||
|
from controller.asset import credential
|
||||||
|
|
||||||
# 当前app的蓝图,以app名为前缀
|
# 当前app的蓝图,以app名为前缀
|
||||||
asset = Blueprint('asset', __name__, url_prefix="/asset")
|
asset = Blueprint('asset', __name__, url_prefix="/asset")
|
||||||
|
@ -11,17 +14,19 @@ api = Api(asset)
|
||||||
api.add_resource(views.HostViews, '/host/', endpoint="host")
|
api.add_resource(views.HostViews, '/host/', endpoint="host")
|
||||||
api.add_resource(views.HostDetailViews, '/host/<string:pk>/', endpoint="host-detail")
|
api.add_resource(views.HostDetailViews, '/host/<string:pk>/', endpoint="host-detail")
|
||||||
|
|
||||||
api.add_resource(views.MySQLInstanceViews, '/database/mysql/', endpoint="db-mysql")
|
# 凭据相关视图
|
||||||
api.add_resource(views.MySQLInstanceDetail, '/database/mysql/<string:pk>/', endpoint="db-mysql-detail")
|
api.add_resource(credential.CredentialViews, '/credential/', endpoint="cred-all")
|
||||||
# 数据实例中的库详情
|
api.add_resource(credential.CredentialClassViews, '/credential/<string:class_name>/', endpoint="cred-cls-list")
|
||||||
api.add_resource(views.DatabaseViews, '/database/<string:pk>/db/', endpoint="db-database")
|
api.add_resource(credential.CredentialClassDetail,
|
||||||
api.add_resource(views.DatabaseDetailViews, '/database/<string:pk>/db/<string:db>/', endpoint="db-database-detail")
|
'/credential/<string:class_name>/<string:pk>/', endpoint="cred-cls-detail")
|
||||||
|
|
||||||
api.add_resource(views.RedisInstanceViews, '/database/redis/', endpoint="db-redis")
|
# 中间件相关视图
|
||||||
api.add_resource(views.RedisInstanceDetail, '/database/redis/<string:pk>/', endpoint="db-redis-detail")
|
api.add_resource(middleware.MiddlewareViews, '/middleware/', endpoint="mdw-all")
|
||||||
|
api.add_resource(middleware.MiddlewareClassViews, '/middleware/<string:class_name>/', endpoint="mdw-cls-list")
|
||||||
|
api.add_resource(middleware.MiddlewareClassDetail,
|
||||||
|
'/middleware/<string:class_name>/<string:pk>/', endpoint="mdw-cls-detail")
|
||||||
|
|
||||||
api.add_resource(views.NginxInstanceViews, '/middleware/nginx/', endpoint="middleware-nginx")
|
# 实例相关视图
|
||||||
api.add_resource(views.NginxInstanceDetail, '/middleware/nginx/<string:pk>/', endpoint="middleware-nginx-detail")
|
api.add_resource(instance.InstanceViews, '/instance/', endpoint="ins-all")
|
||||||
|
api.add_resource(instance.InstanceClassViews, '/instance/<string:class_name>/', endpoint="ins-cls-list")
|
||||||
api.add_resource(views.CDNViews, '/cdn/', endpoint="cdn")
|
api.add_resource(instance.InstanceClassDetail, '/instance/<string:class_name>/<string:pk>/', endpoint="ins-cls-detail")
|
||||||
api.add_resource(views.CDNDetail, '/cdn/<string:pk>/', endpoint="cdn-detail")
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
from flask_restful import marshal
|
||||||
|
from common.serializer import marshal as marshal_fields
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
def merge_dict_reduce(x: dict, y: dict):
|
||||||
|
"""合并字典"""
|
||||||
|
x.update(y)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def key_indexed_map(dataset: list, fields, key="id"):
|
||||||
|
"""将数据集合转换为以指定列为 `key` 的数据,可以指定 `key` 为某个字段名
|
||||||
|
如:{
|
||||||
|
"xx01": {"id": "xx01", "field_1": "1", ...},
|
||||||
|
"xx02": {"id": "xx02", "field_1": "1", ...},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
def pk_indexed_map(obj):
|
||||||
|
"""需要是个闭包,使用传入的参数"""
|
||||||
|
idx = str(getattr(obj, key)) if hasattr(obj, key) else ""
|
||||||
|
return {idx: marshal(obj, fields)}
|
||||||
|
|
||||||
|
return reduce(merge_dict_reduce, map(pk_indexed_map, dataset))
|
||||||
|
|
||||||
|
|
||||||
|
def to_list(dataset: list, fields, only_exist=True, **kwargs):
|
||||||
|
"""将数据集合转换为 `list` 对象,按 fields 字段描述转换"""
|
||||||
|
if only_exist:
|
||||||
|
return list(map(lambda x: marshal_fields(x, fields, only_exist=True, **kwargs), dataset))
|
||||||
|
return list(map(lambda x: marshal(x, fields, **kwargs), dataset))
|
|
@ -0,0 +1,60 @@
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
def marshal(data, fields, envelope=None, only_exist=False, required_fields=None, **kwargs):
|
||||||
|
"""Takes raw data (in the form of a dict, list, object) and a dict of
|
||||||
|
fields to output and filters the data based on those fields.
|
||||||
|
|
||||||
|
:param data: the actual object(s) from which the fields are taken from
|
||||||
|
:param fields: a dict of whose keys will make up the final serialized
|
||||||
|
response output
|
||||||
|
:param envelope: optional key that will be used to envelop the serialized
|
||||||
|
response
|
||||||
|
:param only_exist: 只渲染存在的 key
|
||||||
|
:param required_fields: 必须渲染的 key,不会被 only_exist 排除
|
||||||
|
|
||||||
|
|
||||||
|
>>> from flask_restful import fields, marshal
|
||||||
|
>>> data = { 'a': 100, 'b': 'foo' }
|
||||||
|
>>> mfields = { 'a': fields.Raw }
|
||||||
|
|
||||||
|
>>> marshal(data, mfields)
|
||||||
|
OrderedDict([('a', 100)])
|
||||||
|
|
||||||
|
>>> marshal(data, mfields, envelope='data')
|
||||||
|
OrderedDict([('data', OrderedDict([('a', 100)]))])
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if required_fields is None:
|
||||||
|
required_fields = []
|
||||||
|
|
||||||
|
def make(cls):
|
||||||
|
if isinstance(cls, type):
|
||||||
|
return cls()
|
||||||
|
return cls
|
||||||
|
|
||||||
|
if isinstance(data, (list, tuple)):
|
||||||
|
return (OrderedDict([(envelope, [marshal(d, fields) for d in data])])
|
||||||
|
if envelope else [marshal(d, fields) for d in data])
|
||||||
|
|
||||||
|
if not only_exist:
|
||||||
|
items = ((k, marshal(data, v) if isinstance(v, dict)
|
||||||
|
else make(v).output(k, data))
|
||||||
|
for k, v in fields.items())
|
||||||
|
else:
|
||||||
|
def parse(item):
|
||||||
|
k, v = item
|
||||||
|
if isinstance(v, dict):
|
||||||
|
return k, marshal(data, v)
|
||||||
|
if hasattr(data, k) or k in required_fields:
|
||||||
|
return k, make(v).output(k, data)
|
||||||
|
return None, ""
|
||||||
|
# items = ((k, marshal(data, v) if isinstance(v, dict)
|
||||||
|
# else make(v).output(k, data))
|
||||||
|
# for k, v in fields.items())
|
||||||
|
items = ((k, v) for k, v in map(parse, fields.items()) if k is not None)
|
||||||
|
print(items)
|
||||||
|
return OrderedDict([(envelope, OrderedDict(items))]) if envelope else OrderedDict(items)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from typing import List, Dict
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restful import abort, Resource, marshal, fields, reqparse
|
from flask_restful import abort, Resource, marshal, fields, reqparse
|
||||||
|
|
||||||
|
from common import formatter
|
||||||
from common.utils import abort_response
|
from common.utils import abort_response
|
||||||
from common.crypto import quick_crypto
|
from common.crypto import quick_crypto
|
||||||
|
|
||||||
|
@ -60,9 +61,9 @@ class ModelViewBase(Resource):
|
||||||
# method_decorators = [token_header_required, ]
|
# method_decorators = [token_header_required, ]
|
||||||
method_decorators = []
|
method_decorators = []
|
||||||
|
|
||||||
def get_object(self, pk):
|
def get_object(self, pk, **kwargs):
|
||||||
try:
|
try:
|
||||||
return self.model.objects(id=pk).first_or_404(message="resource not found")
|
return self.model.objects(id=pk, **kwargs).first_or_404(message="resource not found")
|
||||||
except:
|
except:
|
||||||
abort(404, msg=f"resource '{pk}' not found")
|
abort(404, msg=f"resource '{pk}' not found")
|
||||||
|
|
||||||
|
@ -117,7 +118,7 @@ class ModelViewBase(Resource):
|
||||||
if errors:
|
if errors:
|
||||||
abort_response(400, 1001, msg=f"部分字段需要唯一,请检查重复!", errors=errors)
|
abort_response(400, 1001, msg=f"部分字段需要唯一,请检查重复!", errors=errors)
|
||||||
|
|
||||||
def validate_fields(self, args: dict, create=True) -> dict:
|
def validate_fields(self, args: dict, create=True, **kwargs) -> dict:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -163,7 +164,7 @@ class ListMixin(ModelViewBase):
|
||||||
# filter 过滤的字段参数
|
# filter 过滤的字段参数
|
||||||
filter_fields = []
|
filter_fields = []
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self, *args, **kwargs):
|
||||||
self.queryset = self.model.objects
|
self.queryset = self.model.objects
|
||||||
|
|
||||||
def filter_queryset(self):
|
def filter_queryset(self):
|
||||||
|
@ -197,21 +198,23 @@ class ListMixin(ModelViewBase):
|
||||||
logger.exception(f"查询出错 {self.model} query_params={query_params}")
|
logger.exception(f"查询出错 {self.model} query_params={query_params}")
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get(self):
|
def get(self, *args, **kwargs):
|
||||||
"""获取列表数据"""
|
"""获取列表数据"""
|
||||||
# 过滤后的数据,
|
# 过滤后的数据,
|
||||||
|
|
||||||
self.get_queryset()
|
self.get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
# TODO 匹配过滤参数
|
|
||||||
self.queryset = self.filter_queryset()
|
self.queryset = self.filter_queryset()
|
||||||
|
limit = request.args.get("limit")
|
||||||
if self.paging:
|
if self.paging and limit != "0":
|
||||||
return self.paginate_queryset(self. queryset)
|
return self.paginate_queryset(self. queryset)
|
||||||
|
|
||||||
# TODO 这里还未了解到比较妥的办法,似乎 `marshal` 返回的信息必须是 `dict`,返回铺平对象的 `[{obj}, {obj}]` 没找到方法,待研究
|
# 转为对象列表
|
||||||
# 不分页,需要取出对象,铺平
|
if self.queryset.count() > 0:
|
||||||
return [marshal(obj, self.fields) for obj in self.queryset]
|
return formatter.to_list(self.queryset, self.fields, only_exist=True)
|
||||||
|
# return marshal(self.queryset, self.fields, envelope="id")
|
||||||
|
# return formatter.key_indexed_map(self.queryset, self.fields)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class CreateMixin(ModelViewBase):
|
class CreateMixin(ModelViewBase):
|
||||||
|
@ -227,7 +230,7 @@ class CreateMixin(ModelViewBase):
|
||||||
|
|
||||||
# 解析参数
|
# 解析参数
|
||||||
args = self.request_parse.parse_args()
|
args = self.request_parse.parse_args()
|
||||||
validated_data = self.validate_fields(args, create=True)
|
validated_data = self.validate_fields(args, create=True, **kwargs)
|
||||||
|
|
||||||
# 校验关联字段
|
# 校验关联字段
|
||||||
self.validate_relation_fields(validated_data)
|
self.validate_relation_fields(validated_data)
|
||||||
|
@ -319,6 +322,67 @@ class DetailViewSet(RetrieveMixin, UpdateMixin, DestroyMixin):
|
||||||
"""带 `pk` 参数的视图集合"""
|
"""带 `pk` 参数的视图集合"""
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveClassMixin(ModelViewBase):
|
||||||
|
|
||||||
|
def get(self, class_name, pk):
|
||||||
|
obj = self.get_object(pk, class_name=class_name)
|
||||||
|
return marshal(obj, self.fields)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateClassMixin(ModelViewBase):
|
||||||
|
|
||||||
|
def pre_update(self, obj, args: dict):
|
||||||
|
"""更新前钩子"""
|
||||||
|
data = {}
|
||||||
|
for k, v in args.items():
|
||||||
|
# 为 None 的不设置
|
||||||
|
if v is not None:
|
||||||
|
data[k] = v
|
||||||
|
return data
|
||||||
|
|
||||||
|
def put(self, class_name, pk):
|
||||||
|
# 获取对象
|
||||||
|
obj = self.get_object(pk, class_name=class_name)
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
args = self.request_parse.parse_args()
|
||||||
|
validated_data = self.validate_fields(args, create=False)
|
||||||
|
# 校验关联字段
|
||||||
|
self.validate_relation_fields(validated_data)
|
||||||
|
validated_data = self.pre_update(obj, validated_data)
|
||||||
|
|
||||||
|
# 检查重复:检查提交的唯一字段值,是否有存在的对象,与当前对象id不同认为异常
|
||||||
|
self.validate_uniq_fields(args, obj=obj)
|
||||||
|
|
||||||
|
# 更新对象、保存
|
||||||
|
try:
|
||||||
|
obj.update(**validated_data)
|
||||||
|
obj.save()
|
||||||
|
# 重新读取数据
|
||||||
|
obj.reload()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"{self.model} 保存对象失败!pk={pk} data={args}")
|
||||||
|
abort_response(500, 1500, msg=f"保存对象失败!{str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
return marshal(obj, self.fields)
|
||||||
|
|
||||||
|
|
||||||
|
class DestroyClassMixin(ModelViewBase):
|
||||||
|
|
||||||
|
def pre_destroy(self, obj):
|
||||||
|
"""删除对象前的方法,可以在这拦截做些操作"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, class_name, pk):
|
||||||
|
"""删除对象方法"""
|
||||||
|
obj = self.get_object(pk, class_name=class_name)
|
||||||
|
# 删除前钩子
|
||||||
|
self.pre_destroy(obj)
|
||||||
|
obj.delete()
|
||||||
|
return {"id": pk}
|
||||||
|
|
||||||
|
|
||||||
class EncryptRequiredCreateView(CreateMixin):
|
class EncryptRequiredCreateView(CreateMixin):
|
||||||
"""创建对象时,加密字段的视图"""
|
"""创建对象时,加密字段的视图"""
|
||||||
# 需要加密的字段
|
# 需要加密的字段
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
from flask import request
|
||||||
|
from flask_restful import reqparse
|
||||||
|
|
||||||
|
from models.asset.credential import Credential
|
||||||
|
from models.asset.credentialFields import CredentialFields, CredentialDetailFields
|
||||||
|
from common.views import (ListCreateViewSet, DetailViewSet,
|
||||||
|
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin)
|
||||||
|
from common.utils import abort_response
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialParse:
|
||||||
|
"""凭据信息的校验类"""
|
||||||
|
request_parse = None
|
||||||
|
# 给子类用的唯一字段,用于校验
|
||||||
|
# uniq_fields = ("name", "host")
|
||||||
|
|
||||||
|
def init_parse(self):
|
||||||
|
self.request_parse = reqparse.RequestParser()
|
||||||
|
|
||||||
|
self.request_parse.add_argument("class_name", required=False, type=str, location='json')
|
||||||
|
|
||||||
|
self.request_parse.add_argument("extra", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("data", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("tags", required=False, type=list, location='json')
|
||||||
|
self.request_parse.add_argument("labels", required=False, type=dict, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialClassNameValidate:
|
||||||
|
"""统一校验 class_name"""
|
||||||
|
def validate_fields(self, args: dict, create=False, **kwargs) -> dict:
|
||||||
|
class_name = kwargs.get("class_name")
|
||||||
|
class_name_params = args.get("class_name")
|
||||||
|
if class_name_params and class_name_params != class_name:
|
||||||
|
return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!")
|
||||||
|
args["class_name"] = class_name
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialViews(CredentialParse, ListCreateViewSet):
|
||||||
|
"""凭据列表、创建视图"""
|
||||||
|
model = Credential
|
||||||
|
fields = CredentialFields
|
||||||
|
filter_fields = (("name", "icontains"), )
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
print(request.method)
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialDetail(CredentialParse, CredentialClassNameValidate, DetailViewSet):
|
||||||
|
"""凭据详情"""
|
||||||
|
model = Credential
|
||||||
|
fields = CredentialDetailFields
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=False, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialClassViews(CredentialParse, CredentialClassNameValidate, ListCreateViewSet):
|
||||||
|
"""凭据列表,带 class_name 参数"""
|
||||||
|
model = Credential
|
||||||
|
fields = CredentialFields
|
||||||
|
filter_fields = (("name", "icontains"),)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialClassDetail(CredentialParse, CredentialClassNameValidate,
|
||||||
|
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin):
|
||||||
|
"""凭据详情,带 class_name 和 pk 参数"""
|
||||||
|
model = Credential
|
||||||
|
fields = CredentialDetailFields
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=False, type=str, location='json')
|
|
@ -0,0 +1,94 @@
|
||||||
|
from flask_restful import reqparse
|
||||||
|
|
||||||
|
from models.asset.middleware import Middleware
|
||||||
|
from models.asset.instance import Instance
|
||||||
|
from models.asset.instanceFields import InstanceFields, InstanceDetailFields
|
||||||
|
from common.views import (ListCreateViewSet, DetailViewSet,
|
||||||
|
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin)
|
||||||
|
from common.utils import abort_response
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceParse:
|
||||||
|
"""
|
||||||
|
中间件模型公共字段的校验类
|
||||||
|
"""
|
||||||
|
request_parse = None
|
||||||
|
# 给子类用的唯一字段,用于校验
|
||||||
|
uniq_fields = (("name", "class_name"),)
|
||||||
|
|
||||||
|
def init_parse(self):
|
||||||
|
self.request_parse = reqparse.RequestParser()
|
||||||
|
self.request_parse.add_argument("class_name", required=False, type=str, location='json')
|
||||||
|
self.request_parse.add_argument("middleware_id", required=False, type=str, location='json')
|
||||||
|
self.request_parse.add_argument("credentials", required=False, type=list, location='json')
|
||||||
|
|
||||||
|
self.request_parse.add_argument("extra", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("data", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("tags", required=False, type=list, location='json')
|
||||||
|
self.request_parse.add_argument("labels", required=False, type=dict, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceClassNameValidate:
|
||||||
|
"""统一校验 class_name和关联的中间件,若中间件 ID 设置值,class_name 需要相同"""
|
||||||
|
def validate_fields(self, args: dict, create=False, **kwargs) -> dict:
|
||||||
|
# 校验 class_name,非必填参数,但是在URL中有传递
|
||||||
|
# 若URL中的参数与请求体中的参数不一致,将抛错(最好是请求体中不带,以URL中的为准)
|
||||||
|
class_name = kwargs.get("class_name")
|
||||||
|
class_name_params = args.get("class_name")
|
||||||
|
if class_name_params and class_name_params != class_name:
|
||||||
|
return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!")
|
||||||
|
args["class_name"] = class_name
|
||||||
|
|
||||||
|
# 校验 middleware_id 若设置值,检查 middleware 对象是否存在,且类型与 instance 一致
|
||||||
|
middleware_id = args.get("middleware_id")
|
||||||
|
if middleware_id:
|
||||||
|
try:
|
||||||
|
assert Middleware.objects(id=middleware_id, class_name=class_name).first()
|
||||||
|
except:
|
||||||
|
return abort_response(400, 1400, msg="参数中的同类型 middleware 对象不存在")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceViews(InstanceParse, ListCreateViewSet):
|
||||||
|
model = Instance
|
||||||
|
fields = InstanceFields
|
||||||
|
filter_fields = (("name", "icontains"), ("class_name", ""))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceDetailViews(InstanceParse, DetailViewSet):
|
||||||
|
model = Instance
|
||||||
|
fields = InstanceDetailFields
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=False, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceClassViews(InstanceParse, InstanceClassNameValidate, ListCreateViewSet):
|
||||||
|
"""某类别的实例列表"""
|
||||||
|
model = Instance
|
||||||
|
fields = InstanceFields
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
self.queryset = self.model.objects(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceClassDetail(InstanceParse, InstanceClassNameValidate, RetrieveClassMixin, UpdateClassMixin, DestroyClassMixin):
|
||||||
|
"""某类别的实例详情"""
|
||||||
|
model = Instance
|
||||||
|
fields = InstanceDetailFields
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=False, type=str, location='json')
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
self.queryset = self.model.objects(**kwargs)
|
|
@ -0,0 +1,90 @@
|
||||||
|
from flask_restful import reqparse
|
||||||
|
|
||||||
|
from models.asset.middleware import Middleware
|
||||||
|
from models.asset.middlewareFields import MiddlewareFields
|
||||||
|
from common.utils import abort_response
|
||||||
|
from common.views import (ListCreateViewSet, ListMixin, DetailViewSet,
|
||||||
|
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin)
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareParse:
|
||||||
|
"""
|
||||||
|
中间件模型公共字段的校验类
|
||||||
|
"""
|
||||||
|
request_parse = None
|
||||||
|
# 给子类用的唯一字段,用于校验
|
||||||
|
uniq_fields = ("name", "host")
|
||||||
|
|
||||||
|
def init_parse(self):
|
||||||
|
self.request_parse = reqparse.RequestParser()
|
||||||
|
|
||||||
|
self.request_parse.add_argument("class_name", required=False, type=str, location='json')
|
||||||
|
self.request_parse.add_argument("host", required=False, type=str, location='json')
|
||||||
|
self.request_parse.add_argument("port", required=False, type=int, location='json')
|
||||||
|
self.request_parse.add_argument("manage", required=False, type=str, location='json')
|
||||||
|
|
||||||
|
self.request_parse.add_argument("extra", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("data", required=False, type=dict, location='json')
|
||||||
|
self.request_parse.add_argument("tags", required=False, type=list, location='json')
|
||||||
|
self.request_parse.add_argument("labels", required=False, type=dict, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareClassNameValidate:
|
||||||
|
"""统一校验 class_name"""
|
||||||
|
def validate_fields(self, args: dict, create=False, **kwargs) -> dict:
|
||||||
|
class_name = kwargs.get("class_name")
|
||||||
|
class_name_params = args.get("class_name")
|
||||||
|
if class_name_params and class_name_params != class_name:
|
||||||
|
return abort_response(400, 1400, msg="URL中的类别与内容中的类别字段有差异!")
|
||||||
|
args["class_name"] = class_name
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareViews(MiddlewareParse, ListCreateViewSet):
|
||||||
|
model = Middleware
|
||||||
|
fields = MiddlewareFields
|
||||||
|
uniq_fields = ("name",)
|
||||||
|
filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains"), ("class_name", ""))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareDetail(MiddlewareParse, DetailViewSet):
|
||||||
|
"""分类别的视图"""
|
||||||
|
model = Middleware
|
||||||
|
fields = MiddlewareFields
|
||||||
|
uniq_fields = ("name",)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareClassViews(MiddlewareParse, MiddlewareClassNameValidate, ListCreateViewSet):
|
||||||
|
"""按分类的中间件视图,带 class_name 参数"""
|
||||||
|
model = Middleware
|
||||||
|
fields = MiddlewareFields
|
||||||
|
uniq_fields = ("name",)
|
||||||
|
filter_fields = (("name", "icontains"), ("host", "icontains"), ("manage", "icontains"))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=True, type=str, location='json')
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
"""按分类查找"""
|
||||||
|
self.queryset = self.model.objects(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareClassDetail(MiddlewareParse, MiddlewareClassNameValidate,
|
||||||
|
RetrieveClassMixin, DestroyClassMixin, UpdateClassMixin):
|
||||||
|
"""分类别的详情视图,带 class_name 和 pk 参数"""
|
||||||
|
model = Middleware
|
||||||
|
fields = MiddlewareFields
|
||||||
|
uniq_fields = ("name",)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.init_parse()
|
||||||
|
self.request_parse.add_argument("name", required=False, type=str, location='json')
|
|
@ -0,0 +1,13 @@
|
||||||
|
import mongoengine as mongo
|
||||||
|
from common.document import DocumentBase
|
||||||
|
|
||||||
|
|
||||||
|
class Credential(DocumentBase):
|
||||||
|
"""凭据信息"""
|
||||||
|
meta = {'allow_inheritance': True, 'collection': 'credential'}
|
||||||
|
class_name = mongo.StringField(required=False, default="")
|
||||||
|
name = mongo.StringField(required=True)
|
||||||
|
extra = mongo.DictField(required=False)
|
||||||
|
data = mongo.DictField(default=dict)
|
||||||
|
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
|
||||||
|
labels = mongo.DictField(default=dict)
|
|
@ -0,0 +1,15 @@
|
||||||
|
from flask_restful import fields
|
||||||
|
|
||||||
|
|
||||||
|
CredentialFields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"name": fields.String,
|
||||||
|
"class_name": fields.String,
|
||||||
|
"extra": fields.Raw,
|
||||||
|
"credentials": fields.List(fields.String),
|
||||||
|
"data": fields.Raw,
|
||||||
|
"tags": fields.List(fields.String),
|
||||||
|
"labels": fields.Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
CredentialDetailFields = CredentialFields
|
|
@ -38,6 +38,18 @@ DatabaseServerFields = {
|
||||||
"labels": fields.Raw,
|
"labels": fields.Raw,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
databaseDetailFields = {
|
||||||
|
"id": "",
|
||||||
|
"name": fields.String,
|
||||||
|
"domain": fields.String,
|
||||||
|
"host": fields.String,
|
||||||
|
"manage": fields.String,
|
||||||
|
"data": fields.Raw,
|
||||||
|
"tags": fields.List(fields.String),
|
||||||
|
"labels": fields.Raw,
|
||||||
|
}
|
||||||
|
|
||||||
DatabaseFields = {
|
DatabaseFields = {
|
||||||
"id": fields.String,
|
"id": fields.String,
|
||||||
"name": fields.String,
|
"name": fields.String,
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import mongoengine as mongo
|
||||||
|
|
||||||
|
from common.document import DocumentBase
|
||||||
|
from models.asset.middleware import Middleware
|
||||||
|
|
||||||
|
|
||||||
|
class Instance(DocumentBase):
|
||||||
|
""""""
|
||||||
|
meta = {'allow_inheritance': True, 'collection': 'instance'}
|
||||||
|
# 实例的分类名称
|
||||||
|
class_name = mongo.StringField(required=False, default="")
|
||||||
|
|
||||||
|
# 实例的名称,可能是库名、仓库名等
|
||||||
|
name = mongo.StringField(required=True)
|
||||||
|
|
||||||
|
# 关联的 middleware
|
||||||
|
middleware_id = mongo.ObjectIdField(required=False)
|
||||||
|
|
||||||
|
# 对应可以有多个凭据
|
||||||
|
credentials = mongo.ListField(mongo.ObjectIdField, default=list)
|
||||||
|
# 按类型不同的信息存放到 extra 字典
|
||||||
|
extra = mongo.DictField(default=dict)
|
||||||
|
|
||||||
|
data = mongo.DictField(default=dict)
|
||||||
|
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
|
||||||
|
labels = mongo.DictField(default=dict)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def middleware(self):
|
||||||
|
if self.middleware_id:
|
||||||
|
try:
|
||||||
|
return Middleware.objects(id=self.middleware_id).first()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
|
@ -0,0 +1,30 @@
|
||||||
|
from flask_restful import fields
|
||||||
|
|
||||||
|
from models.asset.middlewareFields import MiddlewareFields
|
||||||
|
|
||||||
|
# 普通视图,只返回内部字段
|
||||||
|
InstanceFields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"name": fields.String,
|
||||||
|
"class_name": fields.String,
|
||||||
|
"middleware_id": fields.String,
|
||||||
|
|
||||||
|
"extra": fields.Raw,
|
||||||
|
"data": fields.Raw,
|
||||||
|
"tags": fields.List(fields.String),
|
||||||
|
"labels": fields.Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 详情视图,返回中间件和凭据
|
||||||
|
InstanceDetailFields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"name": fields.String,
|
||||||
|
"class_name": fields.String,
|
||||||
|
"middleware": fields.Nested(MiddlewareFields),
|
||||||
|
"credentials": fields.List(fields.String),
|
||||||
|
|
||||||
|
"extra": fields.Raw,
|
||||||
|
"data": fields.Raw,
|
||||||
|
"tags": fields.List(fields.String),
|
||||||
|
"labels": fields.Raw,
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import mongoengine as mongo
|
||||||
|
|
||||||
|
from common.document import DocumentBase
|
||||||
|
|
||||||
|
|
||||||
|
class Middleware(DocumentBase):
|
||||||
|
"""中间件"""
|
||||||
|
meta = {'allow_inheritance': True, 'collection': 'mdw', 'strict': False}
|
||||||
|
# 分类名称
|
||||||
|
class_name = mongo.StringField(required=False, default="")
|
||||||
|
|
||||||
|
name = mongo.StringField(required=True)
|
||||||
|
host = mongo.StringField(required=False, default="")
|
||||||
|
port = mongo.IntField(default=0)
|
||||||
|
# 管理者,预留
|
||||||
|
manage = mongo.StringField(max_length=128, required=False, default="")
|
||||||
|
extra = mongo.DictField(default=dict)
|
||||||
|
data = mongo.DictField(default=dict)
|
||||||
|
# 标记和标签
|
||||||
|
tags = mongo.ListField(mongo.StringField(), default=list) # tags 默认是空列表
|
||||||
|
labels = mongo.DictField(default=dict)
|
||||||
|
|
||||||
|
|
||||||
|
# class DBServer(Middleware):
|
||||||
|
# """数据库服务器"""
|
||||||
|
# meta = {'allow_inheritance': True, 'strict': False}
|
||||||
|
# domain = mongo.StringField(required=False) # 域名连接的地址
|
||||||
|
# credentials = mongo.ListField(mongo.ObjectIdField, default=list)
|
||||||
|
|
||||||
|
# 配置
|
||||||
|
# storage = mongo.IntField(required=False, default=0)
|
||||||
|
# memory = mongo.IntField(required=False, default=0)
|
||||||
|
# core = mongo.IntField(required=False, default=0)
|
|
@ -0,0 +1,38 @@
|
||||||
|
from flask_restful import fields
|
||||||
|
|
||||||
|
MiddlewareFields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"name": fields.String,
|
||||||
|
"host": fields.String,
|
||||||
|
"port": fields.Integer,
|
||||||
|
"manage": fields.String,
|
||||||
|
"class_name": fields.String,
|
||||||
|
"extra": fields.Raw,
|
||||||
|
"credentials": fields.List(fields.String),
|
||||||
|
"data": fields.Raw,
|
||||||
|
"tags": fields.List(fields.String),
|
||||||
|
"labels": fields.Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
# DatabaseFields = {
|
||||||
|
# "id": fields.String,
|
||||||
|
# "name": fields.String,
|
||||||
|
# "username": fields.String,
|
||||||
|
# "password": fields.String,
|
||||||
|
# "data": fields.Raw,
|
||||||
|
# "tags": fields.List(fields.String),
|
||||||
|
# "labels": fields.Raw,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# DBServerFields = {
|
||||||
|
# "domain": fields.String,
|
||||||
|
# "port": fields.Integer,
|
||||||
|
# "username": fields.String,
|
||||||
|
# "password": fields.String,
|
||||||
|
# "storage": fields.Integer,
|
||||||
|
# "memory": fields.Integer,
|
||||||
|
# "core": fields.Integer,
|
||||||
|
# "databases": fields.List(fields.Nested(DatabaseFields))
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# DBServerFields.update(MiddlewareFields)
|
|
@ -44,6 +44,7 @@ ChannelFields = {
|
||||||
"id": fields.String,
|
"id": fields.String,
|
||||||
"project": fields.Nested(ProjectSimpleField),
|
"project": fields.Nested(ProjectSimpleField),
|
||||||
"name": fields.String,
|
"name": fields.String,
|
||||||
|
"fullname": fields.String,
|
||||||
"spid": fields.String,
|
"spid": fields.String,
|
||||||
"is_cross": fields.Boolean,
|
"is_cross": fields.Boolean,
|
||||||
"version": fields.Nested(VersionFields),
|
"version": fields.Nested(VersionFields),
|
||||||
|
|
|
@ -81,6 +81,13 @@ class Channel(DocumentBase):
|
||||||
if isinstance(val, Project):
|
if isinstance(val, Project):
|
||||||
self.project_id = str(val.id)
|
self.project_id = str(val.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullname(self):
|
||||||
|
project = ""
|
||||||
|
if self.project:
|
||||||
|
project = self.project.fullname
|
||||||
|
return f"{self.spid} {project}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_by_project(cls, name, fork, queryset=None):
|
def filter_by_project(cls, name, fork, queryset=None):
|
||||||
"""过滤项目的所有渠道"""
|
"""过滤项目的所有渠道"""
|
||||||
|
|
Loading…
Reference in New Issue