feat(api): ci baseline rollback (#498)

This commit is contained in:
pycook 2024-04-28 19:19:14 +08:00 committed by GitHub
parent b4326722e6
commit 7d9ef229c2
18 changed files with 302 additions and 101 deletions

View File

@ -441,10 +441,13 @@ class CIManager(object):
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
raw_dict = copy.deepcopy(ci_dict)
attrs = CITypeAttributeManager.get_all_attributes(ci.type_id) attrs = CITypeAttributeManager.get_all_attributes(ci.type_id)
ci_type_attrs_name = {attr.name: attr for _, attr in attrs} ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
ci_type_attrs_alias2name = {attr.alias: attr.name for _, attr in attrs}
ci_dict = {ci_type_attrs_alias2name[k] if k in ci_type_attrs_alias2name else k: v for k, v in ci_dict.items()}
raw_dict = copy.deepcopy(ci_dict)
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs} ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
for _, attr in attrs: for _, attr in attrs:
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT: if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
@ -825,6 +828,105 @@ class CIManager(object):
return data.get('v') return data.get('v')
def baseline(self, ci_ids, before_date):
ci_list = self.get_cis_by_ids(ci_ids, ret_key=RetKey.ALIAS)
if not ci_list:
return dict()
ci2changed = dict()
changed = AttributeHistoryManger.get_records_for_attributes(
before_date, None, None, 1, 100000, None, None, ci_ids=ci_ids, more=True)[1]
for records in changed:
for change in records[1]:
if change['is_computed'] or change['is_password']:
continue
if change.get('default') and change['default'].get('default') == AttributeDefaultValueEnum.UPDATED_AT:
continue
ci2changed.setdefault(change['ci_id'], {})
item = (change['old'],
change['new'],
change.get('is_list'),
change.get('value_type'),
change['operate_type'])
if change.get('is_list'):
ci2changed[change['ci_id']].setdefault(change.get('attr_alias'), []).append(item)
else:
ci2changed[change['ci_id']].update({change.get('attr_alias'): item})
type2show_name = {}
result = []
for ci in ci_list:
list_attr2item = {}
for alias_name, v in (ci2changed.get(ci['_id']) or {}).items():
if not alias_name:
continue
if alias_name == ci.get('unique_alias'):
continue
if ci.get('_type') not in type2show_name:
ci_type = CITypeCache.get(ci.get('_type'))
show_id = ci_type.show_id or ci_type.unique_id
type2show_name[ci['_type']] = AttributeCache.get(show_id).alias
if isinstance(v, list):
for old, new, is_list, value_type, operate_type in v:
if alias_name not in list_attr2item:
list_attr2item[alias_name] = dict(instance=ci.get(type2show_name[ci['_type']]),
attr_name=alias_name,
value_type=value_type,
is_list=is_list,
ci_type=ci.get('ci_type'),
unique_alias=ci.get('unique_alias'),
unique_value=ci.get(ci['unique_alias']),
cur=copy.deepcopy(ci.get(alias_name)),
to=ci.get(alias_name) or [])
old = ValueTypeMap.deserialize[value_type](old) if old else old
new = ValueTypeMap.deserialize[value_type](new) if new else new
if operate_type == OperateType.ADD:
list_attr2item[alias_name]['to'].remove(new)
elif operate_type == OperateType.DELETE and old not in list_attr2item[alias_name]['to']:
list_attr2item[alias_name]['to'].append(old)
continue
old, value_type = v[0], v[3]
old = ValueTypeMap.deserialize[value_type](old) if old else old
if isinstance(old, (datetime.datetime, datetime.date)):
old = str(old)
if ci.get(alias_name) != old:
item = dict(instance=ci.get(type2show_name[ci['_type']]),
attr_name=alias_name,
value_type=value_type,
ci_type=ci.get('ci_type'),
unique_alias=ci.get('unique_alias'),
unique_value=ci.get(ci['unique_alias']),
cur=ci.get(alias_name),
to=old)
result.append(item)
for alias_name, item in list_attr2item.items():
if sorted(item['cur'] or []) != sorted(item['to'] or []):
result.append(item)
return result
def rollback(self, ci_id, before_date):
baseline_ci = self.baseline([ci_id], before_date)
payload = dict()
for item in baseline_ci:
payload[item.get('attr_name')] = item.get('to')
if payload:
payload['ci_type'] = baseline_ci[0]['ci_type']
payload[baseline_ci[0]['unique_alias']] = baseline_ci[0]['unique_value']
self.update(ci_id, **payload)
return payload
class CIRelationManager(object): class CIRelationManager(object):
""" """

View File

@ -253,6 +253,13 @@ class CITypeManager(object):
for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False): for item in CITypeInheritance.get_by(child_id=type_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)
try:
from api.models.cmdb import CITypeReconciliation
for item in CITypeReconciliation.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False)
except Exception:
pass
db.session.commit() db.session.commit()
ci_type.soft_delete() ci_type.soft_delete()
@ -414,9 +421,6 @@ class CITypeGroupManager(object):
existed = CITypeGroup.get_by_id(gid) or abort( existed = CITypeGroup.get_by_id(gid) or abort(
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid))) 404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
if name is not None and name != existed.name: if name is not None and name != existed.name:
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))
existed.update(name=name) existed.update(name=name)
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0]) max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])

View File

@ -55,6 +55,9 @@ class CITypeOperateType(BaseEnum):
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一 DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
ADD_RELATION = "12" # 新增关系 ADD_RELATION = "12" # 新增关系
DELETE_RELATION = "13" # 删除关系 DELETE_RELATION = "13" # 删除关系
ADD_RECONCILIATION = "14" # 删除关系
UPDATE_RECONCILIATION = "15" # 删除关系
DELETE_RECONCILIATION = "16" # 删除关系
class RetKey(BaseEnum): class RetKey(BaseEnum):
@ -98,6 +101,12 @@ class AttributeDefaultValueEnum(BaseEnum):
AUTO_INC_ID = "$auto_inc_id" AUTO_INC_ID = "$auto_inc_id"
class ExecuteStatusEnum(BaseEnum):
COMPLETED = '0'
FAILED = '1'
RUNNING = '2'
CMDB_QUEUE = "one_cmdb_async" CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION" REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"

View File

@ -26,7 +26,7 @@ from api.models.cmdb import OperationRecord
class AttributeHistoryManger(object): class AttributeHistoryManger(object):
@staticmethod @staticmethod
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id, def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
ci_id=None, attr_id=None): ci_id=None, attr_id=None, ci_ids=None, more=False):
records = db.session.query(OperationRecord, AttributeHistory).join( records = db.session.query(OperationRecord, AttributeHistory).join(
AttributeHistory, OperationRecord.id == AttributeHistory.record_id) AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
@ -48,6 +48,9 @@ class AttributeHistoryManger(object):
if ci_id is not None: if ci_id is not None:
records = records.filter(AttributeHistory.ci_id == ci_id) records = records.filter(AttributeHistory.ci_id == ci_id)
if ci_ids and isinstance(ci_ids, list):
records = records.filter(AttributeHistory.ci_id.in_(ci_ids))
if attr_id is not None: if attr_id is not None:
records = records.filter(AttributeHistory.attr_id == attr_id) records = records.filter(AttributeHistory.attr_id == attr_id)
@ -62,6 +65,12 @@ class AttributeHistoryManger(object):
if attr_hist['attr']: if attr_hist['attr']:
attr_hist['attr_name'] = attr_hist['attr'].name attr_hist['attr_name'] = attr_hist['attr'].name
attr_hist['attr_alias'] = attr_hist['attr'].alias attr_hist['attr_alias'] = attr_hist['attr'].alias
if more:
attr_hist['is_list'] = attr_hist['attr'].is_list
attr_hist['is_computed'] = attr_hist['attr'].is_computed
attr_hist['is_password'] = attr_hist['attr'].is_password
attr_hist['default'] = attr_hist['attr'].default
attr_hist['value_type'] = attr_hist['attr'].value_type
attr_hist.pop("attr") attr_hist.pop("attr")
if record_id not in res: if record_id not in res:
@ -161,6 +170,7 @@ class AttributeHistoryManger(object):
record = i.OperationRecord record = i.OperationRecord
item = dict(attr_name=attr.name, item = dict(attr_name=attr.name,
attr_alias=attr.alias, attr_alias=attr.alias,
value_type=attr.value_type,
operate_type=hist.operate_type, operate_type=hist.operate_type,
username=user and user.nickname, username=user and user.nickname,
old=hist.old, old=hist.old,
@ -271,7 +281,7 @@ class CITypeHistoryManager(object):
return numfound, result return numfound, result
@staticmethod @staticmethod
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None): def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None, rc_id=None):
if type_id is None and attr_id is not None: if type_id is None and attr_id is not None:
from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeAttribute
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)] type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
@ -284,6 +294,7 @@ class CITypeHistoryManager(object):
uid=current_user.uid, uid=current_user.uid,
attr_id=attr_id, attr_id=attr_id,
trigger_id=trigger_id, trigger_id=trigger_id,
rc_id=rc_id,
unique_constraint_id=unique_constraint_id, unique_constraint_id=unique_constraint_id,
change=change) change=change)

View File

@ -78,6 +78,8 @@ class ErrFormat(CommonErrFormat):
unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued") unique_constraint_invalid = _l("Uniquely constrained attributes cannot be JSON and multi-valued")
ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器 ci_type_trigger_duplicate = _l("Duplicated trigger") # 重复的触发器
ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在 ci_type_trigger_not_found = _l("Trigger {} does not exist") # 触发器 {} 不存在
ci_type_reconciliation_duplicate = _l("Duplicated reconciliation rule") # 重复的校验规则
ci_type_reconciliation_not_found = _l("Reconciliation rule {} does not exist") # 规则 {} 不存在
record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在 record_not_found = _l("Operation record {} does not exist") # 操作记录 {} 不存在
cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识 cannot_delete_unique = _l("Unique identifier cannot be deleted") # 不能删除唯一标识

View File

@ -275,34 +275,27 @@ class AttributeValueManager(object):
if attr.is_list: if attr.is_list:
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False) 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] existed_values = [i.value for i in existed_attrs]
added = set(value) - set(existed_values)
deleted = set(existed_values) - set(value)
for v in added:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
# Comparison array starts from which position changes
min_len = min(len(value), len(existed_values))
index = 0
while index < min_len:
if value[index] != existed_values[index]:
break
index += 1
added = value[index:]
deleted = existed_values[index:]
# Delete first and then add to ensure id sorting
for v in deleted: for v in deleted:
existed_attr = existed_attrs[existed_values.index(v)] existed_attr = existed_attrs[existed_values.index(v)]
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id)) changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
for v in added:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.ADD, None, v, ci.type_id))
else: else:
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False) 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 existed_value = existed_attr and existed_attr.value
existed_value = (ValueTypeMap.serialize[attr.value_type](existed_value) if
existed_value or existed_value == 0 else existed_value)
if existed_value is None and value is not None: if existed_value is None and value is not None:
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False) value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id)) changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
else: else:
if existed_value != value: if existed_value != value and existed_attr:
if value is None: if value is None:
existed_attr.delete(flush=False, commit=False) existed_attr.delete(flush=False, commit=False)
else: else:

View File

@ -224,16 +224,22 @@ class RoleRelationCache(object):
@classmethod @classmethod
@flush_db @flush_db
def rebuild(cls, rid, app_id): def rebuild(cls, rid, app_id):
cls.clean(rid, app_id) if app_id is None:
app_ids = [None] + [i.id for i in App.get_by(to_dict=False)]
cls.get_parent_ids(rid, app_id)
cls.get_child_ids(rid, app_id)
resources = cls.get_resources(rid, app_id)
if resources.get('id2perms') or resources.get('group2perms'):
HasResourceRoleCache.add(rid, app_id)
else: else:
HasResourceRoleCache.remove(rid, app_id) app_ids = [app_id]
cls.get_resources2(rid, app_id)
for _app_id in app_ids:
cls.clean(rid, _app_id)
cls.get_parent_ids(rid, _app_id)
cls.get_child_ids(rid, _app_id)
resources = cls.get_resources(rid, _app_id)
if resources.get('id2perms') or resources.get('group2perms'):
HasResourceRoleCache.add(rid, _app_id)
else:
HasResourceRoleCache.remove(rid, _app_id)
cls.get_resources2(rid, _app_id)
@classmethod @classmethod
@flush_db @flush_db

View File

@ -274,12 +274,14 @@ class PermissionCRUD(object):
perm2resource.setdefault(_perm, []).append(resource_id) perm2resource.setdefault(_perm, []).append(resource_id)
for _perm in perm2resource: for _perm in perm2resource:
perm = PermissionCache.get(_perm, resource_type_id) perm = PermissionCache.get(_perm, resource_type_id)
existeds = RolePermission.get_by(rid=rid, if perm is None:
app_id=app_id, continue
perm_id=perm.id, exists = RolePermission.get_by(rid=rid,
__func_in___key_resource_id=perm2resource[_perm], app_id=app_id,
to_dict=False) perm_id=perm.id,
for existed in existeds: __func_in___key_resource_id=perm2resource[_perm],
to_dict=False)
for existed in exists:
existed.deleted = True existed.deleted = True
existed.deleted_at = datetime.datetime.now() existed.deleted_at = datetime.datetime.now()
db.session.add(existed) db.session.add(existed)

View File

@ -2,7 +2,6 @@
from flask import abort from flask import abort
from flask import current_app
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@ -127,11 +126,18 @@ class ResourceTypeCRUD(object):
existed_ids = [i.id for i in existed] existed_ids = [i.id for i in existed]
current_ids = [] current_ids = []
rebuild_rids = set()
for i in existed: for i in existed:
if i.name not in perms: if i.name not in perms:
i.soft_delete() i.soft_delete(commit=False)
for rp in RolePermission.get_by(perm_id=i.id, to_dict=False):
rp.soft_delete(commit=False)
rebuild_rids.add((rp.app_id, rp.rid))
else: else:
current_ids.append(i.id) current_ids.append(i.id)
db.session.commit()
for _app_id, _rid in rebuild_rids:
role_rebuild.apply_async(args=(_rid, _app_id), queue=ACL_QUEUE)
for i in perms: for i in perms:
if i not in existed_names: if i not in existed_names:

View File

@ -3,12 +3,14 @@
import time import time
import redis_lock
import six import six
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from sqlalchemy import or_ from sqlalchemy import or_
from api.extensions import db from api.extensions import db
from api.extensions import rd
from api.lib.perm.acl.app import AppCRUD from api.lib.perm.acl.app import AppCRUD
from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope from api.lib.perm.acl.audit import AuditCRUD, AuditOperateType, AuditScope
from api.lib.perm.acl.cache import AppCache from api.lib.perm.acl.cache import AppCache
@ -62,7 +64,9 @@ class RoleRelationCRUD(object):
id2parents = {} id2parents = {}
for i in res: for i in res:
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict()) parent = RoleCache.get(i.parent_id)
if parent:
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(parent.to_dict())
return id2parents return id2parents
@ -141,24 +145,27 @@ class RoleRelationCRUD(object):
@classmethod @classmethod
def add(cls, role, parent_id, child_ids, app_id): def add(cls, role, parent_id, child_ids, app_id):
result = [] with redis_lock.Lock(rd.r, "ROLE_RELATION_ADD"):
for child_id in child_ids: db.session.commit()
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
if existed:
continue
RoleRelationCache.clean(parent_id, app_id) result = []
RoleRelationCache.clean(child_id, app_id) for child_id in child_ids:
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, app_id=app_id)
if existed:
continue
if parent_id in cls.recursive_child_ids(child_id, app_id): RoleRelationCache.clean(parent_id, app_id)
return abort(400, ErrFormat.inheritance_dead_loop) RoleRelationCache.clean(child_id, app_id)
if app_id is None: if parent_id in cls.recursive_child_ids(child_id, app_id):
for app in AppCRUD.get_all(): return abort(400, ErrFormat.inheritance_dead_loop)
if app.name != "acl":
RoleRelationCache.clean(child_id, app.id)
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict()) if app_id is None:
for app in AppCRUD.get_all():
if app.name != "acl":
RoleRelationCache.clean(child_id, app.id)
result.append(RoleRelation.create(parent_id=parent_id, child_id=child_id, app_id=app_id).to_dict())
AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add, AuditCRUD.add_role_log(app_id, AuditOperateType.role_relation_add,
AuditScope.role_relation, role.id, {}, {}, AuditScope.role_relation, role.id, {}, {},

View File

@ -3,12 +3,13 @@
import json import json
import re import re
from celery_once import QueueOnce import redis_lock
from flask import current_app from flask import current_app
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from api.extensions import celery from api.extensions import celery
from api.extensions import rd
from api.lib.decorator import flush_db from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db from api.lib.decorator import reconnect_db
from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.audit import AuditCRUD
@ -25,14 +26,14 @@ from api.models.acl import Role
from api.models.acl import Trigger from api.models.acl import Trigger
@celery.task(name="acl.role_rebuild", @celery.task(name="acl.role_rebuild", queue=ACL_QUEUE, )
queue=ACL_QUEUE,)
@flush_db @flush_db
@reconnect_db @reconnect_db
def role_rebuild(rids, app_id): def role_rebuild(rids, app_id):
rids = rids if isinstance(rids, list) else [rids] rids = rids if isinstance(rids, list) else [rids]
for rid in rids: for rid in rids:
RoleRelationCache.rebuild(rid, app_id) with redis_lock.Lock(rd.r, "ROLE_REBUILD_{}_{}".format(rid, app_id)):
RoleRelationCache.rebuild(rid, app_id)
current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id)) current_app.logger.info("Role {0} App {1} rebuild..........".format(rids, app_id))

View File

@ -16,6 +16,7 @@ from api.lib.cmdb.const import RetKey
from api.lib.cmdb.perms import has_perm_for_ci from api.lib.cmdb.perms import has_perm_for_ci
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
from api.lib.decorator import args_required
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.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
@ -254,3 +255,23 @@ class CIPasswordView(APIView):
def post(self, ci_id, attr_id): def post(self, ci_id, attr_id):
return self.get(ci_id, attr_id) return self.get(ci_id, attr_id)
class CIBaselineView(APIView):
url_prefix = ("/ci/baseline", "/ci/<int:ci_id>/baseline/rollback")
@args_required("before_date")
def get(self):
ci_ids = handle_arg_list(request.values.get('ci_ids'))
before_date = request.values.get('before_date')
return self.jsonify(CIManager().baseline(list(map(int, ci_ids)), before_date))
@args_required("before_date")
def post(self, ci_id):
if 'rollback' in request.url:
before_date = request.values.get('before_date')
return self.jsonify(**CIManager().rollback(ci_id, before_date))
return self.get(ci_id)

View File

@ -7,7 +7,6 @@ from io import BytesIO
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask import session
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache from api.lib.cmdb.cache import CITypeCache
@ -23,6 +22,8 @@ from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
from api.lib.cmdb.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
@ -36,6 +37,8 @@ from api.lib.perm.auth import auth_with_app_token
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CITypeView(APIView): class CITypeView(APIView):
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>", url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>",
@ -125,7 +128,8 @@ class CITypeGroupView(APIView):
return self.jsonify(CITypeGroupManager.get(need_other, config_required)) return self.jsonify(CITypeGroupManager.get(need_other, config_required))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.create_CIType_group, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def post(self): def post(self):
@ -134,12 +138,11 @@ class CITypeGroupView(APIView):
return self.jsonify(group.to_dict()) return self.jsonify(group.to_dict())
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.update_CIType_group, app_cli.admin_name)
@args_validate(CITypeGroupManager.cls) @args_validate(CITypeGroupManager.cls)
def put(self, gid=None): def put(self, gid=None):
if "/order" in request.url: if "/order" in request.url:
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))
group_ids = request.values.get('group_ids') group_ids = request.values.get('group_ids')
CITypeGroupManager.order(group_ids) CITypeGroupManager.order(group_ids)
@ -152,7 +155,8 @@ class CITypeGroupView(APIView):
return self.jsonify(gid=gid) return self.jsonify(gid=gid)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.delete_CIType_group, app_cli.admin_name)
def delete(self, gid): def delete(self, gid):
type_ids = request.values.get("type_ids") type_ids = request.values.get("type_ids")
CITypeGroupManager.delete(gid, type_ids) CITypeGroupManager.delete(gid, type_ids)
@ -352,14 +356,16 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView): class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export") url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def get(self, type_id=None): # export def get(self, type_id=None): # export
if type_id is not None: if type_id is not None:
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id))) return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id)))
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template())) return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def post(self): # import def post(self): # import
tpt = request.values.get('ci_type_template') or {} tpt = request.values.get('ci_type_template') or {}
@ -379,7 +385,8 @@ class CITypeCanDefineComputed(APIView):
class CITypeTemplateFileView(APIView): class CITypeTemplateFileView(APIView):
url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file") url_prefix = ("/ci_types/template/import/file", "/ci_types/template/export/file")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def get(self): # export def get(self): # export
tpt_json = CITypeTemplateManager.export_template() tpt_json = CITypeTemplateManager.export_template()
tpt_json = dict(ci_type_template=tpt_json) tpt_json = dict(ci_type_template=tpt_json)
@ -394,7 +401,8 @@ class CITypeTemplateFileView(APIView):
mimetype='application/json', mimetype='application/json',
max_age=0) max_age=0)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Configuration,
app_cli.op.download_CIType, app_cli.admin_name)
def post(self): # import def post(self): # import
f = request.files.get('file') f = request.files.get('file')

View File

@ -11,6 +11,8 @@ 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.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import has_perm_from_args from api.lib.perm.acl.acl import has_perm_from_args
@ -18,6 +20,8 @@ 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.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class GetChildrenView(APIView): class GetChildrenView(APIView):
url_prefix = ("/ci_type_relations/<int:parent_id>/children", url_prefix = ("/ci_type_relations/<int:parent_id>/children",
@ -41,7 +45,8 @@ class GetParentsView(APIView):
class CITypeRelationView(APIView): class CITypeRelationView(APIView):
url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>") url_prefix = ("/ci_type_relations", "/ci_type_relations/<int:parent_id>/<int:child_id>")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
res, type2attributes = CITypeRelationManager.get() res, type2attributes = CITypeRelationManager.get()
@ -69,7 +74,8 @@ class CITypeRelationView(APIView):
class CITypeRelationDelete2View(APIView): class CITypeRelationDelete2View(APIView):
url_prefix = "/ci_type_relations/<int:ctr_id>" url_prefix = "/ci_type_relations/<int:ctr_id>"
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Model_Relationships,
app_cli.op.read, app_cli.admin_name)
def delete(self, ctr_id): def delete(self, ctr_id):
CITypeRelationManager.delete(ctr_id) CITypeRelationManager.delete(ctr_id)

View File

@ -3,14 +3,16 @@
from flask import request from flask import request
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.custom_dashboard import CustomDashboardManager from api.lib.cmdb.custom_dashboard import CustomDashboardManager
from api.lib.cmdb.custom_dashboard import SystemConfigManager from api.lib.cmdb.custom_dashboard import SystemConfigManager
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class CustomDashboardApiView(APIView): class CustomDashboardApiView(APIView):
url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch", url_prefix = ("/custom_dashboard", "/custom_dashboard/<int:_id>", "/custom_dashboard/batch",
@ -19,7 +21,8 @@ class CustomDashboardApiView(APIView):
def get(self): def get(self):
return self.jsonify(CustomDashboardManager.get()) return self.jsonify(CustomDashboardManager.get())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
@args_validate(CustomDashboardManager.cls) @args_validate(CustomDashboardManager.cls)
def post(self): def post(self):
if request.url.endswith("/preview"): if request.url.endswith("/preview"):
@ -32,7 +35,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(res) return self.jsonify(res)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
@args_validate(CustomDashboardManager.cls) @args_validate(CustomDashboardManager.cls)
def put(self, _id=None): def put(self, _id=None):
if _id is not None: if _id is not None:
@ -47,7 +51,8 @@ class CustomDashboardApiView(APIView):
return self.jsonify(id2options=request.values.get('id2options')) return self.jsonify(id2options=request.values.get('id2options'))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Customized_Dashboard,
app_cli.op.read, app_cli.admin_name)
def delete(self, _id): def delete(self, _id):
CustomDashboardManager.delete(_id) CustomDashboardManager.delete(_id)
@ -57,12 +62,14 @@ class CustomDashboardApiView(APIView):
class SystemConfigApiView(APIView): class SystemConfigApiView(APIView):
url_prefix = ("/system_config",) url_prefix = ("/system_config",)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name", value_required=True) @args_required("name", value_required=True)
def get(self): def get(self):
return self.jsonify(SystemConfigManager.get(request.values['name'])) return self.jsonify(SystemConfigManager.get(request.values['name']))
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_validate(SystemConfigManager.cls) @args_validate(SystemConfigManager.cls)
@args_required("name", value_required=True) @args_required("name", value_required=True)
@args_required("option", value_required=True) @args_required("option", value_required=True)
@ -74,7 +81,8 @@ class SystemConfigApiView(APIView):
def put(self, _id=None): def put(self, _id=None):
return self.post() return self.post()
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def delete(self): def delete(self):
CustomDashboardManager.delete(request.values['name']) CustomDashboardManager.delete(request.values['name'])

View File

@ -5,28 +5,29 @@ 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.history import AttributeHistoryManger from api.lib.cmdb.history import AttributeHistoryManger
from api.lib.cmdb.history import CITriggerHistoryManager 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.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
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.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
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RecordView(APIView): class RecordView(APIView):
url_prefix = ("/history/records/attribute", "/history/records/relation") url_prefix = ("/history/records/attribute", "/history/records/relation")
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@ -80,18 +81,21 @@ class CIHistoryView(APIView):
class CITriggerHistoryView(APIView): class CITriggerHistoryView(APIView):
url_prefix = ("/history/ci_triggers/<int:ci_id>", "/history/ci_triggers") url_prefix = ("/history/ci_triggers/<int:ci_id>",)
@has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name) @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.READ, CIManager.get_type_name)
def get(self, ci_id=None): def get(self, ci_id):
if ci_id is not None: result = CITriggerHistoryManager.get_by_ci_id(ci_id)
result = CITriggerHistoryManager.get_by_ci_id(ci_id)
return self.jsonify(result) 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))
class CIsTriggerHistoryView(APIView):
url_prefix = ("/history/ci_triggers",)
@perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self):
type_id = request.values.get("type_id") type_id = request.values.get("type_id")
trigger_id = request.values.get("trigger_id") trigger_id = request.values.get("trigger_id")
operate_type = request.values.get("operate_type") operate_type = request.values.get("operate_type")
@ -115,7 +119,8 @@ class CITriggerHistoryView(APIView):
class CITypeHistoryView(APIView): class CITypeHistoryView(APIView):
url_prefix = "/history/ci_types" url_prefix = "/history/ci_types"
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Operation_Audit,
app_cli.op.read, app_cli.admin_name)
def get(self): def get(self):
type_id = request.values.get("type_id") type_id = request.values.get("type_id")
username = request.values.get("username") username = request.values.get("username")

View File

@ -8,20 +8,22 @@ from flask import request
from api.lib.cmdb.ci_type import CITypeManager from api.lib.cmdb.ci_type import CITypeManager
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.perms import CIFilterPermsCRUD from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.preference import PreferenceManager from api.lib.cmdb.preference import PreferenceManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
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 is_app_admin
from api.lib.perm.acl.acl import role_required
from api.lib.perm.acl.acl import validate_permission from api.lib.perm.acl.acl import validate_permission
from api.lib.utils import handle_arg_list from api.lib.utils import handle_arg_list
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class PreferenceShowCITypesView(APIView): class PreferenceShowCITypesView(APIView):
url_prefix = ("/preference/ci_types", "/preference/ci_types2") url_prefix = ("/preference/ci_types", "/preference/ci_types2")
@ -104,7 +106,8 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_required("cr_ids") @args_required("cr_ids")
@args_validate(PreferenceManager.pref_rel_cls) @args_validate(PreferenceManager.pref_rel_cls)
@ -118,14 +121,16 @@ class PreferenceRelationApiView(APIView):
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def put(self, _id): def put(self, _id):
views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values) views, id2type, name2id = PreferenceManager.create_or_update_relation_view(_id=_id, **request.values)
return self.jsonify(views=views, id2type=id2type, name2id=name2id) return self.jsonify(views=views, id2type=id2type, name2id=name2id)
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Service_Tree_Definition,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
def delete(self): def delete(self):
name = request.values.get("name") name = request.values.get("name")

View File

@ -4,14 +4,16 @@
from flask import abort from flask import abort
from flask import request from flask import request
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.relation_type import RelationTypeManager from api.lib.cmdb.relation_type import RelationTypeManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.common_setting.decorator import perms_role_required
from api.lib.common_setting.role_perm_base import CMDBApp
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import role_required
from api.resource import APIView from api.resource import APIView
app_cli = CMDBApp()
class RelationTypeView(APIView): class RelationTypeView(APIView):
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>") url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
@ -19,7 +21,8 @@ class RelationTypeView(APIView):
def get(self): def get(self):
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()]) return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def post(self): def post(self):
@ -28,7 +31,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) return self.jsonify(rel.to_dict())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
@args_required("name") @args_required("name")
@args_validate(RelationTypeManager.cls) @args_validate(RelationTypeManager.cls)
def put(self, rel_id): def put(self, rel_id):
@ -37,7 +41,8 @@ class RelationTypeView(APIView):
return self.jsonify(rel.to_dict()) return self.jsonify(rel.to_dict())
@role_required(RoleEnum.CONFIG) @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.Relationship_Types,
app_cli.op.read, app_cli.admin_name)
def delete(self, rel_id): def delete(self, rel_id):
RelationTypeManager.delete(rel_id) RelationTypeManager.delete(rel_id)