refactor: CI triggers

This commit is contained in:
pycook 2023-09-22 17:39:54 +08:00
parent 6e94c72031
commit d8399f8723
12 changed files with 431 additions and 128 deletions

View File

@ -48,6 +48,7 @@ six = "==1.12.0"
bs4 = ">=0.0.1" bs4 = ">=0.0.1"
toposort = ">=1.5" toposort = ">=1.5"
requests = ">=2.22.0" requests = ">=2.22.0"
requests_oauthlib = "==1.3.1"
PyJWT = "==2.4.0" PyJWT = "==2.4.0"
elasticsearch = "==7.17.9" elasticsearch = "==7.17.9"
future = "==0.18.3" future = "==0.18.3"

View File

@ -4,6 +4,7 @@
import copy import copy
import datetime import datetime
import json import json
import threading
from flask import abort from flask import abort
from flask import current_app from flask import current_app
@ -24,27 +25,33 @@ from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import ExistPolicy from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.history import CIRelationHistoryManager from api.lib.cmdb.history import CIRelationHistoryManager
from api.lib.cmdb.history import CITriggerHistoryManager
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.utils import TableMap from api.lib.cmdb.utils import TableMap
from api.lib.cmdb.utils import ValueTypeMap from api.lib.cmdb.utils import ValueTypeMap
from api.lib.cmdb.value import AttributeValueManager from api.lib.cmdb.value import AttributeValueManager
from api.lib.decorator import kwargs_required from api.lib.decorator import kwargs_required
from api.lib.notify import notify_send
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.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import Lock from api.lib.utils import Lock
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.lib.webhook import webhook_request
from api.models.cmdb import AttributeHistory
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import CI from api.models.cmdb import CI
from api.models.cmdb import CIRelation from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeRelation
from api.models.cmdb import CITypeTrigger
from api.tasks.cmdb import ci_cache from api.tasks.cmdb import ci_cache
from api.tasks.cmdb import ci_delete from api.tasks.cmdb import ci_delete
from api.tasks.cmdb import ci_relation_add from api.tasks.cmdb import ci_relation_add
@ -378,16 +385,17 @@ class CIManager(object):
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id, key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr) ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
operate_type = OperateType.UPDATE if ci is not None else OperateType.ADD
try: try:
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery) ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr) record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
except BadRequest as e: except BadRequest as e:
if existed is None: if existed is None:
cls.delete(ci.id) cls.delete(ci.id)
raise e raise e
if record_id: # has change if record_id: # has change
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci.id, operate_type, record_id), queue=CMDB_QUEUE)
if ref_ci_dict: # add relations if ref_ci_dict: # add relations
ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE) ci_relation_add.apply_async(args=(ref_ci_dict, ci.id, current_user.uid), queue=CMDB_QUEUE)
@ -427,12 +435,12 @@ class CIManager(object):
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
try: try:
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr) record_id = value_manager.create_or_update_attr_value(ci, ci_dict, key2attr)
except BadRequest as e: except BadRequest as e:
raise e raise e
if record_id: # has change if record_id: # has change
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k} ref_ci_dict = {k: v for k, v in ci_dict.items() if k.startswith("$") and "." in k}
if ref_ci_dict: if ref_ci_dict:
@ -442,9 +450,10 @@ class CIManager(object):
def update_unique_value(ci_id, unique_name, unique_value): def update_unique_value(ci_id, unique_name, unique_value):
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id))) ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci) key2attr = {unique_name: AttributeCache.get(unique_name)}
record_id = AttributeValueManager().create_or_update_attr_value(ci, {unique_name: unique_value}, key2attr)
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE) ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE)
@classmethod @classmethod
def delete(cls, ci_id): def delete(cls, ci_id):
@ -477,9 +486,9 @@ class CIManager(object):
db.session.commit() db.session.commit()
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id) record_id = AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
ci_delete.apply_async([ci.id], queue=CMDB_QUEUE) ci_delete.apply_async(args=(ci_dict, OperateType.DELETE, record_id), queue=CMDB_QUEUE)
return ci_id return ci_id
@ -896,3 +905,128 @@ class CIRelationManager(object):
for parent_id in parents: for parent_id in parents:
for ci_id in ci_ids: for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id) cls.delete_2(parent_id, ci_id)
class CITriggerManager(object):
@staticmethod
def get(type_id):
return CITypeTrigger.get_by(type_id=type_id, to_dict=False)
@staticmethod
def _exec_webhook(operate_type, webhook, ci_dict, trigger_id, record_id):
try:
response = webhook_request(webhook, ci_dict).text
is_ok = True
except Exception as e:
current_app.logger.warning("exec webhook failed: {}".format(e))
response = e
is_ok = False
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
is_ok=is_ok,
webhook=response)
return is_ok
@staticmethod
def _exec_notify(operate_type, notify, ci_dict, trigger_id, record_id, ci_id=None):
if ci_id is not None:
ci_dict = CIManager().get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
try:
response = notify_send(notify.get('subject'), notify.get('body'), notify.get('tos'), ci_dict)
is_ok = True
except Exception as e:
current_app.logger.warning("send notify failed: {}".format(e))
response = e
is_ok = False
CITriggerHistoryManager.add(operate_type,
record_id,
ci_dict.get('_id'),
trigger_id,
is_ok=is_ok,
notify=response)
return is_ok
@staticmethod
def ci_filter(ci_id, other_filter):
from api.lib.cmdb.search import SearchError
from api.lib.cmdb.search.ci import search
query = "_id:{},{}".format(ci_id, other_filter)
try:
_, _, _, _, numfound, _ = search(query).search()
return numfound
except SearchError as e:
current_app.logger.warning("ci search failed: {}".format(e))
@classmethod
def fire(cls, operate_type, ci_dict, record_id):
type_id = ci_dict.get('_type')
triggers = cls.get(type_id) or []
for trigger in triggers:
if not trigger.option.get('enable'):
continue
if trigger.option.get('filter') and not cls.ci_filter(ci_dict.get('_id'), trigger.option['filter']):
continue
if trigger.option.get('attr_ids') and isinstance(trigger.option['attr_ids'], list):
if not (set(trigger.option['attr_ids']) &
set([i.attr_id for i in AttributeHistory.get_by(record_id=record_id, to_dict=False)])):
continue
if trigger.option.get('action') == operate_type:
if trigger.option.get('webhooks'):
cls._exec_webhook(operate_type, trigger.option['webhooks'], ci_dict, trigger.id, record_id)
elif trigger.option.get('notifies'):
cls._exec_notify(operate_type, trigger.option['notifies'], ci_dict, trigger.id, record_id)
@classmethod
def waiting_cis(cls, trigger):
now = datetime.datetime.today()
delta_time = datetime.timedelta(days=(trigger.option.get('before_days', 0) or 0))
attr = AttributeCache.get(trigger.attr_id)
value_table = TableMap(attr=attr).table
values = value_table.get_by(attr_id=attr.id, to_dict=False)
result = []
for v in values:
if (isinstance(v.value, (datetime.date, datetime.datetime)) and
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d")):
if trigger.option.get('filter') and not cls.ci_filter(v.ci_id, trigger.option['filter']):
continue
result.append(v)
return result
@classmethod
def trigger_notify(cls, trigger, ci):
"""
only for date attribute
:param trigger:
:param ci:
:return:
"""
if (trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
not trigger.option.get('notify_at')):
threading.Thread(target=cls._exec_notify, args=(
None, trigger.option['notifies'], None, trigger.id, None, ci.id)).start()
return True
return False

View File

@ -1166,16 +1166,18 @@ class CITypeUniqueConstraintManager(object):
class CITypeTriggerManager(object): class CITypeTriggerManager(object):
@staticmethod @staticmethod
def get(type_id): def get(type_id, to_dict=True):
return CITypeTrigger.get_by(type_id=type_id, to_dict=True) return CITypeTrigger.get_by(type_id=type_id, to_dict=to_dict)
@staticmethod @staticmethod
def add(type_id, attr_id, notify): def add(type_id, attr_id, option):
CITypeTrigger.get_by(type_id=type_id, attr_id=attr_id) and abort(400, ErrFormat.ci_type_trigger_duplicate) for i in CITypeTrigger.get_by(type_id=type_id, attr_id=attr_id, to_dict=False):
if i.option == option:
return abort(400, ErrFormat.ci_type_trigger_duplicate)
not isinstance(notify, dict) and abort(400, ErrFormat.argument_invalid.format("notify")) not isinstance(option, dict) and abort(400, ErrFormat.argument_invalid.format("option"))
trigger = CITypeTrigger.create(type_id=type_id, attr_id=attr_id, notify=notify) trigger = CITypeTrigger.create(type_id=type_id, attr_id=attr_id, option=option)
CITypeHistoryManager.add(CITypeOperateType.ADD_TRIGGER, CITypeHistoryManager.add(CITypeOperateType.ADD_TRIGGER,
type_id, type_id,
@ -1185,12 +1187,12 @@ class CITypeTriggerManager(object):
return trigger.to_dict() return trigger.to_dict()
@staticmethod @staticmethod
def update(_id, notify): def update(_id, option):
existed = (CITypeTrigger.get_by_id(_id) or existed = (CITypeTrigger.get_by_id(_id) or
abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id)))) abort(404, ErrFormat.ci_type_trigger_not_found.format("id={}".format(_id))))
existed2 = existed.to_dict() existed2 = existed.to_dict()
new = existed.update(notify=notify) new = existed.update(option=option)
CITypeHistoryManager.add(CITypeOperateType.UPDATE_TRIGGER, CITypeHistoryManager.add(CITypeOperateType.UPDATE_TRIGGER,
existed.type_id, existed.type_id,
@ -1210,35 +1212,3 @@ class CITypeTriggerManager(object):
existed.type_id, existed.type_id,
trigger_id=_id, trigger_id=_id,
change=existed.to_dict()) change=existed.to_dict())
@staticmethod
def waiting_cis(trigger):
now = datetime.datetime.today()
delta_time = datetime.timedelta(days=(trigger.notify.get('before_days', 0) or 0))
attr = AttributeCache.get(trigger.attr_id)
value_table = TableMap(attr=attr).table
values = value_table.get_by(attr_id=attr.id, to_dict=False)
result = []
for v in values:
if (isinstance(v.value, (datetime.date, datetime.datetime)) and
(v.value - delta_time).strftime('%Y%m%d') == now.strftime("%Y%m%d")):
result.append(v)
return result
@staticmethod
def trigger_notify(trigger, ci):
if (trigger.notify.get('notify_at') == datetime.datetime.now().strftime("%H:%M") or
not trigger.notify.get('notify_at')):
from api.tasks.cmdb import trigger_notify
trigger_notify.apply_async(args=(trigger.notify, ci.ci_id), queue=CMDB_QUEUE)
return True
return False

View File

@ -16,6 +16,7 @@ from api.lib.perm.acl.cache import UserCache
from api.models.cmdb import Attribute from api.models.cmdb import Attribute
from api.models.cmdb import AttributeHistory from api.models.cmdb import AttributeHistory
from api.models.cmdb import CIRelationHistory from api.models.cmdb import CIRelationHistory
from api.models.cmdb import CITriggerHistory
from api.models.cmdb import CITypeHistory from api.models.cmdb import CITypeHistory
from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeTrigger
from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CITypeUniqueConstraint
@ -286,3 +287,67 @@ class CITypeHistoryManager(object):
change=change) change=change)
CITypeHistory.create(**payload) CITypeHistory.create(**payload)
class CITriggerHistoryManager(object):
@staticmethod
def get(page, page_size, type_id=None, trigger_id=None, operate_type=None):
query = CITriggerHistory.get_by(only_query=True)
if type_id is not None:
query = query.filter(CITriggerHistory.type_id == type_id)
if trigger_id:
query = query.filter(CITriggerHistory.trigger_id == trigger_id)
if operate_type is not None:
query = query.filter(CITriggerHistory.operate_type == operate_type)
numfound = query.count()
query = query.order_by(CITriggerHistory.id.desc())
result = query.offset((page - 1) * page_size).limit(page_size)
result = [i.to_dict() for i in result]
for res in result:
if res.get('trigger_id'):
trigger = CITypeTrigger.get_by_id(res['trigger_id'])
res['trigger'] = trigger and trigger.to_dict()
return numfound, result
@staticmethod
def get_by_ci_id(ci_id):
res = db.session.query(CITriggerHistory, CITypeTrigger, OperationRecord).join(
CITypeTrigger, CITypeTrigger.id == CITriggerHistory.trigger_id).join(
OperationRecord, OperationRecord.id == CITriggerHistory.record_id).filter(
CITriggerHistory.ci_id == ci_id).order_by(CITriggerHistory.id.desc())
result = []
id2trigger = dict()
for i in res:
hist = i.CITriggerHistory
record = i.OperationRecord
item = dict(is_ok=hist.is_ok,
operate_type=hist.operate_type,
notify=hist.notify,
webhook=hist.webhook,
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
record_id=record.id,
hid=hist.id
)
if i.CITypeTrigger.id not in id2trigger:
id2trigger[i.CITypeTrigger.id] = i.CITypeTrigger.to_dict()
result.append(item)
return dict(items=result, id2trigger=id2trigger)
@staticmethod
def add(operate_type, record_id, ci_id, trigger_id, is_ok=False, notify=None, webhook=None):
CITriggerHistory.create(operate_type=operate_type,
record_id=record_id,
ci_id=ci_id,
trigger_id=trigger_id,
is_ok=is_ok,
notify=notify,
webhook=webhook)

View File

@ -18,7 +18,6 @@ from api.extensions import db
from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.attribute import AttributeManager
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeAttributeCache from api.lib.cmdb.cache import CITypeAttributeCache
from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
@ -140,6 +139,7 @@ class AttributeValueManager(object):
try: try:
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback()
current_app.logger.error("write change failed: {}".format(str(e))) current_app.logger.error("write change failed: {}".format(str(e)))
return record_id return record_id
@ -235,7 +235,7 @@ class AttributeValueManager(object):
return key2attr return key2attr
def create_or_update_attr_value2(self, ci, ci_dict, key2attr): def create_or_update_attr_value(self, ci, ci_dict, key2attr):
""" """
add or update attribute value, then write history add or update attribute value, then write history
:param ci: instance object :param ci: instance object
@ -288,66 +288,6 @@ class AttributeValueManager(object):
return self._write_change2(changed) return self._write_change2(changed)
def create_or_update_attr_value(self, key, value, ci, _no_attribute_policy=ExistPolicy.IGNORE, record_id=None):
"""
add or update attribute value, then write history
:param key: id, name or alias
:param value:
:param ci: instance object
:param _no_attribute_policy: ignore or reject
:param record_id: op record
:return:
"""
attr = self._get_attr(key)
if attr is None:
if _no_attribute_policy == ExistPolicy.IGNORE:
return
if _no_attribute_policy == ExistPolicy.REJECT:
return abort(400, ErrFormat.attribute_not_found.format(key))
value_table = TableMap(attr=attr).table
try:
if attr.is_list:
value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)]
if not value_list:
self._check_is_required(ci.type_id, attr, '')
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
existed_values = [i.value for i in existed_attrs]
added = set(value_list) - set(existed_values)
deleted = set(existed_values) - set(value_list)
for v in added:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v)
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, v, record_id, ci.type_id)
for v in deleted:
existed_attr = existed_attrs[existed_values.index(v)]
existed_attr.delete()
record_id = self._write_change(ci.id, attr.id, OperateType.DELETE, v, None, record_id, ci.type_id)
else:
value = self._validate(attr, value, value_table, ci)
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
existed_value = existed_attr and existed_attr.value
if existed_value is None and value is not None:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value)
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, value, record_id, ci.type_id)
else:
if existed_value != value:
if value is None:
existed_attr.delete()
else:
existed_attr.update(value=value)
record_id = self._write_change(ci.id, attr.id, OperateType.UPDATE,
existed_value, value, record_id, ci.type_id)
return record_id
except Exception as e:
current_app.logger.warning(str(e))
return abort(400, ErrFormat.attribute_value_invalid2.format("{}({})".format(attr.alias, attr.name), value))
@staticmethod @staticmethod
def delete_attr_value(attr_id, ci_id): def delete_attr_value(attr_id, ci_id):
attr = AttributeCache.get(attr_id) attr = AttributeCache.get(attr_id)

View File

@ -0,0 +1,45 @@
# -*- coding:utf-8 -*-
import json
import requests
from flask import current_app
from jinja2 import Template
from api.lib.mail import send_mail
def _request_messenger(subject, body, tos, sender):
params = dict(sender=sender, title=subject,
tos=[to[sender] for to in tos if to.get(sender)])
if not params['tos']:
raise Exception("no receivers")
if sender == "email":
params['msgtype'] = 'text/html'
params['content'] = body
else:
params['msgtype'] = 'text'
params['content'] = json.dumps(dict(content=subject or body))
resp = requests.post(current_app.config.get('MESSENGER_URL'), json=params)
if resp.status_code != 200:
raise Exception(resp.text)
return resp.text
def notify_send(subject, body, methods, tos, payload=None):
payload = payload or {}
subject = Template(subject).render(payload)
body = Template(body).render(payload)
res = ''
for method in methods:
if method == "email" and not current_app.config.get('USE_MESSENGER', True):
send_mail(None, [to.get('email') for to in tos], subject, body)
res += _request_messenger(subject, body, tos, method) + "\n"
return res

105
cmdb-api/api/lib/webhook.py Normal file
View File

@ -0,0 +1,105 @@
# -*- coding:utf-8 -*-
import json
from functools import partial
import requests
from jinja2 import Template
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session
class BearerAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, r):
r.headers["authorization"] = "Bearer {}".format(self.token)
return r
def _wrap_auth(**kwargs):
auth_type = (kwargs.get('type') or "").lower()
if auth_type == "basicauth":
return HTTPBasicAuth(kwargs.get('username'), kwargs.get('password'))
elif auth_type == "bearer":
return BearerAuth(kwargs.get('token'))
elif auth_type == 'oauth2.0':
client_id = kwargs.get('client_id')
client_secret = kwargs.get('client_secret')
authorization_base_url = kwargs.get('authorization_base_url')
token_url = kwargs.get('token_url')
redirect_url = kwargs.get('redirect_url')
scope = kwargs.get('scope')
oauth2_session = OAuth2Session(client_id, scope=scope or None)
oauth2_session.authorization_url(authorization_base_url)
oauth2_session.fetch_token(token_url, client_secret=client_secret, authorization_response=redirect_url)
return oauth2_session
elif auth_type == "apikey":
return HTTPBasicAuth(kwargs.get('key'), kwargs.get('value'))
def webhook_request(webhook, payload):
"""
:param webhook:
{
"url": "https://veops.cn"
"method": "GET|POST|PUT|DELETE"
"body": {},
"headers": {
"Content-Type": "Application/json"
},
"parameters": {
"key": "value"
},
"authorization": {
"type": "BasicAuth|Bearer|OAuth2.0|APIKey",
"password": "mmmm", # BasicAuth
"username": "bbb", # BasicAuth
"token": "xxx", # Bearer
"key": "xxx", # APIKey
"value": "xxx", # APIKey
"client_id": "xxx", # OAuth2.0
"client_secret": "xxx", # OAuth2.0
"authorization_base_url": "xxx", # OAuth2.0
"token_url": "xxx", # OAuth2.0
"redirect_url": "xxx", # OAuth2.0
"scope": "xxx" # OAuth2.0
}
}
:param payload:
:return:
"""
assert webhook.get('url') is not None
url = Template(webhook['url']).render(payload)
params = webhook.get('parameters') or None
if isinstance(params, dict):
params = json.loads(Template(json.dumps(params)).render(payload))
data = Template(json.dumps(webhook.get('body', ''))).render(payload)
auth = _wrap_auth(**webhook.get('authorization', {}))
if (webhook.get('authorization', {}).get("type") or '').lower() == 'oauth2.0':
request = getattr(auth, webhook.get('method', 'GET').lower())
else:
request = partial(requests.request, webhook.get('method', 'GET'))
return request(
url,
params=params,
headers=webhook.get('headers') or None,
data=data,
auth=auth
)

View File

@ -125,16 +125,26 @@ class CITypeAttributeGroupItem(Model):
class CITypeTrigger(Model): class CITypeTrigger(Model):
# __tablename__ = "c_ci_type_triggers"
__tablename__ = "c_c_t_t" __tablename__ = "c_c_t_t"
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False) type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False) attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
notify = db.Column(db.JSON) # {subject: x, body: x, wx_to: [], mail_to: [], before_days: 0, notify_at: 08:00} option = db.Column('notify', db.JSON)
class CITriggerHistory(Model):
__tablename__ = "c_ci_trigger_histories"
operate_type = db.Column(db.Enum(*OperateType.all(), name="operate_type"))
record_id = db.Column(db.Integer, db.ForeignKey("c_records.id"))
ci_id = db.Column(db.Integer, index=True, nullable=False)
trigger_id = db.Column(db.Integer, db.ForeignKey("c_c_t_t.id"))
is_ok = db.Column(db.Boolean, default=False)
notify = db.Column(db.Text)
webhook = db.Column(db.Text)
class CITypeUniqueConstraint(Model): class CITypeUniqueConstraint(Model):
# __tablename__ = "c_ci_type_unique_constraints"
__tablename__ = "c_c_t_u_c" __tablename__ = "c_c_t_u_c"
type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False) type_id = db.Column(db.Integer, db.ForeignKey('c_ci_types.id'), nullable=False)
@ -363,7 +373,6 @@ class CITypeHistory(Model):
# preference # preference
class PreferenceShowAttributes(Model): class PreferenceShowAttributes(Model):
# __tablename__ = "c_preference_show_attributes"
__tablename__ = "c_psa" __tablename__ = "c_psa"
uid = db.Column(db.Integer, index=True, nullable=False) uid = db.Column(db.Integer, index=True, nullable=False)
@ -377,7 +386,6 @@ class PreferenceShowAttributes(Model):
class PreferenceTreeView(Model): class PreferenceTreeView(Model):
# __tablename__ = "c_preference_tree_views"
__tablename__ = "c_ptv" __tablename__ = "c_ptv"
uid = db.Column(db.Integer, index=True, nullable=False) uid = db.Column(db.Integer, index=True, nullable=False)
@ -386,7 +394,6 @@ class PreferenceTreeView(Model):
class PreferenceRelationView(Model): class PreferenceRelationView(Model):
# __tablename__ = "c_preference_relation_views"
__tablename__ = "c_prv" __tablename__ = "c_prv"
uid = db.Column(db.Integer, index=True, nullable=False) uid = db.Column(db.Integer, index=True, nullable=False)

View File

@ -185,8 +185,8 @@ class CIUnique(APIView):
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name) @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name)
def put(self, ci_id): def put(self, ci_id):
params = request.values params = request.values
unique_name = params.keys()[0] unique_name = list(params.keys())[0]
unique_value = params.values()[0] unique_value = list(params.values())[0]
CIManager.update_unique_value(ci_id, unique_name, unique_value) CIManager.update_unique_value(ci_id, unique_name, unique_value)

View File

@ -419,22 +419,21 @@ class CITypeTriggerView(APIView):
return self.jsonify(CITypeTriggerManager.get(type_id)) return self.jsonify(CITypeTriggerManager.get(type_id))
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
@args_required("attr_id") @args_required("option")
@args_required("notify")
def post(self, type_id): def post(self, type_id):
attr_id = request.values.get('attr_id') attr_id = request.values.get('attr_id') or None
notify = request.values.get('notify') option = request.values.get('option')
return self.jsonify(CITypeTriggerManager().add(type_id, attr_id, notify)) return self.jsonify(CITypeTriggerManager().add(type_id, attr_id, option))
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
@args_required("notify") @args_required("option")
def put(self, type_id, _id): def put(self, type_id, _id):
assert type_id is not None assert type_id is not None
notify = request.values.get('notify') option = request.values.get('option')
return self.jsonify(CITypeTriggerManager().update(_id, notify)) return self.jsonify(CITypeTriggerManager().update(_id, option))
@has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id) @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.CONFIG, CITypeManager.get_name_by_id)
def delete(self, type_id, _id): def delete(self, type_id, _id):

View File

@ -5,15 +5,18 @@ import datetime
from flask import abort from flask import abort
from flask import request from flask import request
from flask import session
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.history import CITriggerHistoryManager
from api.lib.cmdb.history import CITypeHistoryManager from api.lib.cmdb.history import CITypeHistoryManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
from api.lib.perm.acl.acl import is_app_admin
from api.lib.perm.acl.acl import role_required from api.lib.perm.acl.acl import role_required
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
@ -76,6 +79,39 @@ class CIHistoryView(APIView):
return self.jsonify(result) return self.jsonify(result)
class CITriggerHistoryView(APIView):
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers")
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
def get(self, ci_id=None):
if ci_id is not None:
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
return self.jsonify(result)
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
type_id = request.values.get("type_id")
trigger_id = request.values.get("trigger_id")
operate_type = request.values.get("operate_type")
page = get_page(request.values.get('page', 1))
page_size = get_page_size(request.values.get('page_size', 1))
numfound, result = CITriggerHistoryManager.get(page,
page_size,
type_id=type_id,
trigger_id=trigger_id,
operate_type=operate_type)
return self.jsonify(page=page,
page_size=page_size,
numfound=numfound,
total=len(result),
result=result)
class CITypeHistoryView(APIView): class CITypeHistoryView(APIView):
url_prefix = "/history/ci_types" url_prefix = "/history/ci_types"

View File

@ -36,6 +36,7 @@ python-ldap==3.4.0
PyYAML==6.0 PyYAML==6.0
redis==4.6.0 redis==4.6.0
requests==2.31.0 requests==2.31.0
requests_oauthlib==1.3.1
six==1.12.0 six==1.12.0
SQLAlchemy==1.4.49 SQLAlchemy==1.4.49
supervisor==4.0.3 supervisor==4.0.3