mirror of https://github.com/veops/cmdb.git
feat: The definition of attribute choice values supports webhook and other model attribute values.
This commit is contained in:
parent
c0541b7e50
commit
ef90506916
|
@ -1,6 +1,5 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import session
|
||||
|
@ -23,6 +22,7 @@ from api.lib.cmdb.utils import ValueTypeMap
|
|||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.webhook import webhook_request
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
|
@ -40,15 +40,11 @@ class AttributeManager(object):
|
|||
pass
|
||||
|
||||
@staticmethod
|
||||
def _get_choice_values_from_web_hook(choice_web_hook):
|
||||
url = choice_web_hook.get('url')
|
||||
ret_key = choice_web_hook.get('ret_key')
|
||||
headers = choice_web_hook.get('headers') or {}
|
||||
payload = choice_web_hook.get('payload') or {}
|
||||
method = (choice_web_hook.get('method') or 'GET').lower()
|
||||
def _get_choice_values_from_webhook(choice_webhook, payload=None):
|
||||
ret_key = choice_webhook.get('ret_key')
|
||||
|
||||
try:
|
||||
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
||||
res = webhook_request(choice_webhook, payload or {}).json()
|
||||
if ret_key:
|
||||
ret_key_list = ret_key.strip().split("##")
|
||||
for key in ret_key_list[:-1]:
|
||||
|
@ -63,16 +59,41 @@ class AttributeManager(object):
|
|||
current_app.logger.error("get choice values failed: {}".format(e))
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _get_choice_values_from_other_ci(choice_other):
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
type_ids = choice_other.get('type_ids')
|
||||
attr_id = choice_other.get('attr_id')
|
||||
other_filter = choice_other.get('filter') or ''
|
||||
|
||||
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
|
||||
s = search(query, fl=[str(attr_id)], facet=[str(attr_id)], count=1)
|
||||
try:
|
||||
_, _, _, _, _, facet = s.search()
|
||||
return [[i[0], {}] for i in (list(facet.values()) or [[]])[0]]
|
||||
except SearchError as e:
|
||||
current_app.logger.error("get choice values from other ci failed: {}".format(e))
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
|
||||
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_other,
|
||||
choice_web_hook_parse=True, choice_other_parse=True):
|
||||
if choice_web_hook:
|
||||
if choice_web_hook_parse:
|
||||
if isinstance(choice_web_hook, dict):
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
if choice_web_hook_parse and isinstance(choice_web_hook, dict):
|
||||
return cls._get_choice_values_from_webhook(choice_web_hook)
|
||||
else:
|
||||
return []
|
||||
elif choice_other:
|
||||
if choice_other_parse and isinstance(choice_other, dict):
|
||||
return cls._get_choice_values_from_other_ci(choice_other)
|
||||
else:
|
||||
return []
|
||||
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
if not choice_table:
|
||||
return []
|
||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||
|
||||
return [[choice_value['value'], choice_value['option']] for choice_value in choice_values]
|
||||
|
@ -122,7 +143,8 @@ class AttributeManager(object):
|
|||
res = list()
|
||||
for attr in attrs:
|
||||
attr["is_choice"] and attr.update(
|
||||
dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))))
|
||||
attr['is_choice'] and attr.pop('choice_web_hook', None)
|
||||
|
||||
res.append(attr)
|
||||
|
@ -132,29 +154,38 @@ class AttributeManager(object):
|
|||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"], attr["choice_web_hook"])
|
||||
attr["choice_value"] = self.get_choice_values(attr["id"], attr["value_type"],
|
||||
attr["choice_web_hook"], attr.get("choice_other"))
|
||||
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key, choice_web_hook_parse=True):
|
||||
def get_attribute(self, key, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
if attr.get("is_choice"):
|
||||
attr["choice_value"] = self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"], choice_web_hook_parse=choice_web_hook_parse)
|
||||
attr["id"],
|
||||
attr["value_type"],
|
||||
attr["choice_web_hook"],
|
||||
attr.get("choice_other"),
|
||||
choice_web_hook_parse=choice_web_hook_parse,
|
||||
choice_other_parse=choice_other_parse,
|
||||
)
|
||||
|
||||
return attr
|
||||
|
||||
|
@ -181,12 +212,17 @@ class AttributeManager(object):
|
|||
def add(cls, **kwargs):
|
||||
choice_value = kwargs.pop("choice_value", [])
|
||||
kwargs.pop("is_choice", None)
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') or kwargs.get('choice_other') else False
|
||||
|
||||
name = kwargs.pop("name")
|
||||
if name in BUILTIN_KEYWORDS:
|
||||
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
|
||||
|
||||
if kwargs.get('choice_other'):
|
||||
if (not isinstance(kwargs['choice_other'], dict) or not kwargs['choice_other'].get('type_ids') or
|
||||
not kwargs['choice_other'].get('attr_id')):
|
||||
return abort(400, ErrFormat.attribute_choice_other_invalid)
|
||||
|
||||
alias = kwargs.pop("alias", "")
|
||||
alias = name if not alias else alias
|
||||
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
||||
|
@ -301,12 +337,17 @@ class AttributeManager(object):
|
|||
|
||||
self._change_index(attr, attr.is_index, kwargs['is_index'])
|
||||
|
||||
if kwargs.get('choice_other'):
|
||||
if (not isinstance(kwargs['choice_other'], dict) or not kwargs['choice_other'].get('type_ids') or
|
||||
not kwargs['choice_other'].get('attr_id')):
|
||||
return abort(400, ErrFormat.attribute_choice_other_invalid)
|
||||
|
||||
existed2 = attr.to_dict()
|
||||
if not existed2['choice_web_hook'] and existed2['is_choice']:
|
||||
existed2['choice_value'] = self.get_choice_values(attr.id, attr.value_type, attr.choice_web_hook)
|
||||
if not existed2['choice_web_hook'] and not existed2.get('choice_other') and existed2['is_choice']:
|
||||
existed2['choice_value'] = self.get_choice_values(attr.id, attr.value_type, None, None)
|
||||
|
||||
choice_value = kwargs.pop("choice_value", False)
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') or kwargs.get('choice_other') else False
|
||||
kwargs['is_choice'] = is_choice
|
||||
|
||||
if kwargs.get('default') and not (isinstance(kwargs['default'], dict) and 'default' in kwargs['default']):
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
import toposort
|
||||
from flask import abort
|
||||
|
@ -25,7 +24,6 @@ from api.lib.cmdb.const import ValueTypeEnum
|
|||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
|
@ -354,19 +352,20 @@ class CITypeAttributeManager(object):
|
|||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributesCache.get(type_id)]
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True):
|
||||
def get_attributes_by_type_id(type_id, choice_web_hook_parse=True, choice_other_parse=True):
|
||||
has_config_perm = ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
attrs = CITypeAttributesCache.get(type_id)
|
||||
result = list()
|
||||
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
|
||||
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse)
|
||||
attr_dict = AttributeManager().get_attribute(attr.attr_id, choice_web_hook_parse, choice_other_parse)
|
||||
attr_dict["is_required"] = attr.is_required
|
||||
attr_dict["order"] = attr.order
|
||||
attr_dict["default_show"] = attr.default_show
|
||||
if not has_config_perm:
|
||||
attr_dict.pop('choice_web_hook', None)
|
||||
attr_dict.pop('choice_other', None)
|
||||
|
||||
result.append(attr_dict)
|
||||
|
||||
|
@ -374,13 +373,25 @@ class CITypeAttributeManager(object):
|
|||
|
||||
@staticmethod
|
||||
def get_common_attributes(type_ids):
|
||||
has_config_perm = False
|
||||
for type_id in type_ids:
|
||||
has_config_perm |= ACLManager('cmdb').has_permission(
|
||||
CITypeManager.get_name_by_id(type_id), ResourceTypeEnum.CI, PermEnum.CONFIG)
|
||||
|
||||
result = CITypeAttribute.get_by(__func_in___key_type_id=list(map(int, type_ids)), to_dict=False)
|
||||
attr2types = {}
|
||||
for i in result:
|
||||
attr2types.setdefault(i.attr_id, []).append(i.type_id)
|
||||
|
||||
return [AttributeCache.get(attr_id).to_dict() for attr_id in attr2types
|
||||
if len(attr2types[attr_id]) == len(type_ids)]
|
||||
attrs = []
|
||||
for attr_id in attr2types:
|
||||
if len(attr2types[attr_id]) == len(type_ids):
|
||||
attr = AttributeManager().get_attribute_by_id(attr_id)
|
||||
if not has_config_perm:
|
||||
attr.pop('choice_web_hook', None)
|
||||
attrs.append(attr)
|
||||
|
||||
return attrs
|
||||
|
||||
@staticmethod
|
||||
def _check(type_id, attr_ids):
|
||||
|
@ -489,7 +500,7 @@ class CITypeAttributeManager(object):
|
|||
for ci in CI.get_by(type_id=type_id, to_dict=False):
|
||||
AttributeValueManager.delete_attr_value(attr_id, ci.id)
|
||||
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
ci_cache.apply_async(args=(ci.id, None, None), queue=CMDB_QUEUE)
|
||||
|
||||
CITypeAttributeCache.clean(type_id, attr_id)
|
||||
|
||||
|
@ -522,7 +533,7 @@ class CITypeAttributeManager(object):
|
|||
CITypeAttributesCache.clean(type_id)
|
||||
|
||||
from api.tasks.cmdb import ci_type_attribute_order_rebuild
|
||||
ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE)
|
||||
ci_type_attribute_order_rebuild.apply_async(args=(type_id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
|
||||
class CITypeRelationManager(object):
|
||||
|
@ -847,7 +858,7 @@ class CITypeAttributeGroupManager(object):
|
|||
CITypeAttributesCache.clean(type_id)
|
||||
|
||||
from api.tasks.cmdb import ci_type_attribute_order_rebuild
|
||||
ci_type_attribute_order_rebuild.apply_async(args=(type_id,), queue=CMDB_QUEUE)
|
||||
ci_type_attribute_order_rebuild.apply_async(args=(type_id, current_user.uid), queue=CMDB_QUEUE)
|
||||
|
||||
|
||||
class CITypeTemplateManager(object):
|
||||
|
@ -1092,7 +1103,7 @@ class CITypeTemplateManager(object):
|
|||
|
||||
for ci_type in tpt['ci_types']:
|
||||
tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
|
||||
ci_type['id'], choice_web_hook_parse=False)
|
||||
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False)
|
||||
|
||||
tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ class PreferenceManager(object):
|
|||
for i in result:
|
||||
if i["is_choice"]:
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||
i["id"], i["value_type"], i["choice_web_hook"])))
|
||||
i["id"], i["value_type"], i["choice_web_hook"], i.get("choice_other"))))
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class ErrFormat(CommonErrFormat):
|
|||
cannot_edit_attribute = "您没有权限修改该属性!"
|
||||
cannot_delete_attribute = "目前只允许 属性创建人、管理员 删除属性!"
|
||||
attribute_name_cannot_be_builtin = "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
attribute_choice_other_invalid = "预定义值: 其他模型请求参数不合法!"
|
||||
|
||||
ci_not_found = "CI {} 不存在"
|
||||
unique_constraint = "多属性联合唯一校验不通过: {}"
|
||||
|
|
|
@ -92,7 +92,7 @@ class AttributeValueManager(object):
|
|||
|
||||
@staticmethod
|
||||
def _check_is_choice(attr, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook)
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook, attr.choice_other)
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from flask import current_app
|
||||
from jinja2 import Template
|
||||
from markdownify import markdownify as md
|
||||
|
@ -17,7 +18,15 @@ def _request_messenger(subject, body, tos, sender, payload):
|
|||
if not params['tos']:
|
||||
raise Exception("no receivers")
|
||||
|
||||
params['tos'] = [Template(i).render(payload) for i in params['tos'] if i.strip()]
|
||||
flat_tos = []
|
||||
for i in params['tos']:
|
||||
if i.strip():
|
||||
to = Template(i).render(payload)
|
||||
if isinstance(to, list):
|
||||
flat_tos.extend(to)
|
||||
elif isinstance(to, six.string_types):
|
||||
flat_tos.append(to)
|
||||
params['tos'] = flat_tos
|
||||
|
||||
if sender == "email":
|
||||
params['msgtype'] = 'text/html'
|
||||
|
|
|
@ -90,6 +90,7 @@ class Attribute(Model):
|
|||
compute_script = db.Column(db.Text)
|
||||
|
||||
choice_web_hook = db.Column(db.JSON)
|
||||
choice_other = db.Column(db.JSON)
|
||||
|
||||
uid = db.Column(db.Integer, index=True)
|
||||
|
||||
|
|
|
@ -41,10 +41,11 @@ def ci_cache(ci_id, operate_type, record_id):
|
|||
|
||||
current_app.logger.info("{0} flush..........".format(ci_id))
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
if operate_type:
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get('worker'))
|
||||
|
||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||
CITriggerManager.fire(operate_type, ci_dict, record_id)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)
|
||||
|
@ -164,7 +165,7 @@ def ci_relation_delete(parent_id, child_id):
|
|||
|
||||
|
||||
@celery.task(name="cmdb.ci_type_attribute_order_rebuild", queue=CMDB_QUEUE)
|
||||
def ci_type_attribute_order_rebuild(type_id):
|
||||
def ci_type_attribute_order_rebuild(type_id, uid):
|
||||
current_app.logger.info('rebuild attribute order')
|
||||
db.session.remove()
|
||||
|
||||
|
@ -173,6 +174,9 @@ def ci_type_attribute_order_rebuild(type_id):
|
|||
attrs = CITypeAttributesCache.get(type_id)
|
||||
id2attr = {attr.attr_id: attr for attr in attrs}
|
||||
|
||||
current_app.test_request_context().push()
|
||||
login_user(UserCache.get(uid))
|
||||
|
||||
res = CITypeAttributeGroupManager.get_by_type_id(type_id, True)
|
||||
order = 0
|
||||
for group in res:
|
||||
|
|
|
@ -506,4 +506,3 @@ class CITypeFilterPermissionView(APIView):
|
|||
@auth_with_app_token
|
||||
def get(self, type_id):
|
||||
return self.jsonify(CIFilterPermsCRUD().get(type_id))
|
||||
|
||||
|
|
Loading…
Reference in New Issue