feat: Predefined values support executing scripts (#227)

This commit is contained in:
pycook 2023-10-24 19:32:43 +08:00 committed by GitHub
parent 7d53180a2f
commit 779cd1ea9e
3 changed files with 57 additions and 30 deletions

View File

@ -60,22 +60,33 @@ class AttributeManager(object):
return [] return []
@staticmethod @staticmethod
def _get_choice_values_from_other_ci(choice_other): def _get_choice_values_from_other(choice_other):
from api.lib.cmdb.search import SearchError from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search from api.lib.cmdb.search.ci import search
type_ids = choice_other.get('type_ids') if choice_other.get('type_ids'):
attr_id = choice_other.get('attr_id') type_ids = choice_other.get('type_ids')
other_filter = choice_other.get('filter') or '' attr_id = choice_other.get('attr_id')
other_filter = choice_other.get('filter') or ''
query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter) query = "_type:({}),{}".format(";".join(map(str, type_ids)), other_filter)
s = search(query, fl=[str(attr_id)], facet=[str(attr_id)], count=1) s = search(query, fl=[str(attr_id)], facet=[str(attr_id)], count=1)
try: try:
_, _, _, _, _, facet = s.search() _, _, _, _, _, facet = s.search()
return [[i[0], {}] for i in (list(facet.values()) or [[]])[0]] return [[i[0], {}] for i in (list(facet.values()) or [[]])[0]]
except SearchError as e: except SearchError as e:
current_app.logger.error("get choice values from other ci failed: {}".format(e)) current_app.logger.error("get choice values from other ci failed: {}".format(e))
return [] return []
elif choice_other.get('script'):
try:
x = compile(choice_other['script'], '', "exec")
exec(x)
res = locals()['ChoiceValue']().values() or []
return [[i, {}] for i in res]
except Exception as e:
current_app.logger.error("get choice values from script: {}".format(e))
return []
@classmethod @classmethod
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_other, def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_other,
@ -87,7 +98,7 @@ class AttributeManager(object):
return [] return []
elif choice_other: elif choice_other:
if choice_other_parse and isinstance(choice_other, dict): if choice_other_parse and isinstance(choice_other, dict):
return cls._get_choice_values_from_other_ci(choice_other) return cls._get_choice_values_from_other(choice_other)
else: else:
return [] return []
@ -96,7 +107,8 @@ class AttributeManager(object):
return [] return []
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id) 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] return [[ValueTypeMap.serialize[value_type](choice_value['value']), choice_value['option']]
for choice_value in choice_values]
@staticmethod @staticmethod
def add_choice_values(_id, value_type, choice_values): def add_choice_values(_id, value_type, choice_values):
@ -218,10 +230,15 @@ class AttributeManager(object):
if name in BUILTIN_KEYWORDS: if name in BUILTIN_KEYWORDS:
return abort(400, ErrFormat.attribute_name_cannot_be_builtin) return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
if kwargs.get('choice_other'): while kwargs.get('choice_other'):
if (not isinstance(kwargs['choice_other'], dict) or not kwargs['choice_other'].get('type_ids') or if isinstance(kwargs['choice_other'], dict):
not kwargs['choice_other'].get('attr_id')): if kwargs['choice_other'].get('script'):
return abort(400, ErrFormat.attribute_choice_other_invalid) break
if kwargs['choice_other'].get('type_ids') and kwargs['choice_other'].get('attr_id'):
break
return abort(400, ErrFormat.attribute_choice_other_invalid)
alias = kwargs.pop("alias", "") alias = kwargs.pop("alias", "")
alias = name if not alias else alias alias = name if not alias else alias
@ -232,6 +249,8 @@ class AttributeManager(object):
kwargs.get('is_computed') and cls.can_create_computed_attribute() kwargs.get('is_computed') and cls.can_create_computed_attribute()
kwargs.get('choice_other') and kwargs['choice_other'].get('script') and cls.can_create_computed_attribute()
attr = Attribute.create(flush=True, attr = Attribute.create(flush=True,
name=name, name=name,
alias=alias, alias=alias,
@ -337,10 +356,15 @@ class AttributeManager(object):
self._change_index(attr, attr.is_index, kwargs['is_index']) self._change_index(attr, attr.is_index, kwargs['is_index'])
if kwargs.get('choice_other'): while kwargs.get('choice_other'):
if (not isinstance(kwargs['choice_other'], dict) or not kwargs['choice_other'].get('type_ids') or if isinstance(kwargs['choice_other'], dict):
not kwargs['choice_other'].get('attr_id')): if kwargs['choice_other'].get('script'):
return abort(400, ErrFormat.attribute_choice_other_invalid) break
if kwargs['choice_other'].get('type_ids') and kwargs['choice_other'].get('attr_id'):
break
return abort(400, ErrFormat.attribute_choice_other_invalid)
existed2 = attr.to_dict() existed2 = attr.to_dict()
if not existed2['choice_web_hook'] and not existed2.get('choice_other') and existed2['is_choice']: if not existed2['choice_web_hook'] and not existed2.get('choice_other') and existed2['is_choice']:

View File

@ -28,6 +28,7 @@ from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_TYPE from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_TYPE
from api.lib.cmdb.search.ci.db.query_sql import QUERY_UNION_CI_ATTRIBUTE_IS_NULL from api.lib.cmdb.search.ci.db.query_sql import QUERY_UNION_CI_ATTRIBUTE_IS_NULL
from api.lib.cmdb.utils import TableMap from api.lib.cmdb.utils import TableMap
from api.lib.cmdb.utils import ValueTypeMap
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
@ -524,15 +525,15 @@ class Search(object):
if k: if k:
table_name = TableMap(attr=attr).table_name table_name = TableMap(attr=attr).table_name
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id) query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
# current_app.logger.warning(query_sql)
result = db.session.execute(query_sql).fetchall() result = db.session.execute(query_sql).fetchall()
facet[k] = result facet[k] = result
facet_result = dict() facet_result = dict()
for k, v in facet.items(): for k, v in facet.items():
if not k.startswith('_'): if not k.startswith('_'):
a = getattr(AttributeCache.get(k), self.ret_key) attr = AttributeCache.get(k)
facet_result[a] = [(f[0], f[1], a) for f in v] a = getattr(attr, self.ret_key)
facet_result[a] = [(ValueTypeMap.serialize[attr.value_type](f[0]), f[1], a) for f in v]
return facet_result return facet_result

View File

@ -12,7 +12,7 @@ import api.models.cmdb as model
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
TIME_RE = re.compile(r"^(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d$") TIME_RE = re.compile(r"^20|21|22|23|[0-1]\d:[0-5]\d:[0-5]\d$")
def string2int(x): def string2int(x):
@ -21,7 +21,7 @@ def string2int(x):
def str2datetime(x): def str2datetime(x):
try: try:
return datetime.datetime.strptime(x, "%Y-%m-%d") return datetime.datetime.strptime(x, "%Y-%m-%d").date()
except ValueError: except ValueError:
pass pass
@ -44,8 +44,8 @@ class ValueTypeMap(object):
ValueTypeEnum.FLOAT: float, ValueTypeEnum.FLOAT: float,
ValueTypeEnum.TEXT: lambda x: x if isinstance(x, six.string_types) else str(x), ValueTypeEnum.TEXT: lambda x: x if isinstance(x, six.string_types) else str(x),
ValueTypeEnum.TIME: lambda x: x if isinstance(x, six.string_types) else str(x), ValueTypeEnum.TIME: lambda x: x if isinstance(x, six.string_types) else str(x),
ValueTypeEnum.DATE: lambda x: x.strftime("%Y-%m-%d"), ValueTypeEnum.DATE: lambda x: x.strftime("%Y-%m-%d") if not isinstance(x, six.string_types) else x,
ValueTypeEnum.DATETIME: lambda x: x.strftime("%Y-%m-%d %H:%M:%S"), ValueTypeEnum.DATETIME: lambda x: x.strftime("%Y-%m-%d %H:%M:%S") if not isinstance(x, six.string_types) else x,
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
} }
@ -64,6 +64,8 @@ class ValueTypeMap(object):
ValueTypeEnum.FLOAT: model.FloatChoice, ValueTypeEnum.FLOAT: model.FloatChoice,
ValueTypeEnum.TEXT: model.TextChoice, ValueTypeEnum.TEXT: model.TextChoice,
ValueTypeEnum.TIME: model.TextChoice, ValueTypeEnum.TIME: model.TextChoice,
ValueTypeEnum.DATE: model.TextChoice,
ValueTypeEnum.DATETIME: model.TextChoice,
} }
table = { table = {
@ -97,7 +99,7 @@ class ValueTypeMap(object):
ValueTypeEnum.DATE: 'text', ValueTypeEnum.DATE: 'text',
ValueTypeEnum.TIME: 'text', ValueTypeEnum.TIME: 'text',
ValueTypeEnum.FLOAT: 'float', ValueTypeEnum.FLOAT: 'float',
ValueTypeEnum.JSON: 'object' ValueTypeEnum.JSON: 'object',
} }