mirror of
				https://github.com/veops/cmdb.git
				synced 2025-10-31 19:39:24 +08:00 
			
		
		
		
	| @@ -11,7 +11,7 @@ click = ">=5.0" | ||||
| # Api | ||||
| Flask-RESTful = "==0.3.10" | ||||
| # Database | ||||
| Flask-SQLAlchemy = "==2.5.0" | ||||
| Flask-SQLAlchemy = "==3.0.5" | ||||
| SQLAlchemy = "==1.4.49" | ||||
| PyMySQL = "==1.1.0" | ||||
| redis = "==4.6.0" | ||||
| @@ -69,6 +69,7 @@ lz4 = ">=4.3.2" | ||||
| python-magic = "==0.4.27" | ||||
| jsonpath = "==0.82.2" | ||||
| networkx = ">=3.1" | ||||
| ipaddress = ">=1.0.23" | ||||
|  | ||||
| [dev-packages] | ||||
| # Testing | ||||
|   | ||||
| @@ -114,7 +114,8 @@ class CIManager(object): | ||||
|         ci_type = CITypeCache.get(ci.type_id) | ||||
|         res["ci_type"] = ci_type.name | ||||
|  | ||||
|         res.update(cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key)) | ||||
|         ci_list = cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key) | ||||
|         ci_list and res.update(ci_list[0]) | ||||
|  | ||||
|         res['_type'] = ci_type.id | ||||
|         res['_id'] = ci_id | ||||
| @@ -207,7 +208,7 @@ class CIManager(object): | ||||
|         res['_type'] = ci_type.id | ||||
|         res['ci_type_alias'] = ci_type.alias | ||||
|         res['_id'] = ci_id | ||||
|         res['_updated_at'] = str(ci.updated_at) | ||||
|         res['_updated_at'] = str(ci.updated_at or '') | ||||
|         res['_updated_by'] = ci.updated_by | ||||
|  | ||||
|         return res | ||||
| @@ -571,6 +572,9 @@ class CIManager(object): | ||||
|             for attr_id in password_dict: | ||||
|                 record_id = self.save_password(ci.id, attr_id, password_dict[attr_id], record_id, ci.type_id) | ||||
|  | ||||
|         u = UserCache.get(current_user.uid) | ||||
|         ci.update(updated_at=now, updated_by=u and u.nickname) | ||||
|  | ||||
|         if record_id or has_dynamic:  # has changed | ||||
|             if not _sync: | ||||
|                 ci_cache.apply_async(args=(ci_id, OperateType.UPDATE, record_id), queue=CMDB_QUEUE) | ||||
|   | ||||
| @@ -17,12 +17,14 @@ from api.lib.cmdb.cache import AttributeCache | ||||
| from api.lib.cmdb.cache import CITypeAttributeCache | ||||
| from api.lib.cmdb.cache import CITypeAttributesCache | ||||
| from api.lib.cmdb.cache import CITypeCache | ||||
| from api.lib.cmdb.const import BuiltinModelEnum | ||||
| from api.lib.cmdb.const import CITypeOperateType | ||||
| from api.lib.cmdb.const import CMDB_QUEUE | ||||
| from api.lib.cmdb.const import ConstraintEnum | ||||
| from api.lib.cmdb.const import PermEnum | ||||
| from api.lib.cmdb.const import ResourceTypeEnum | ||||
| from api.lib.cmdb.const import RoleEnum | ||||
| from api.lib.cmdb.const import SysComputedAttributes | ||||
| from api.lib.cmdb.const import ValueTypeEnum | ||||
| from api.lib.cmdb.history import CITypeHistoryManager | ||||
| from api.lib.cmdb.perms import CIFilterPermsCRUD | ||||
| @@ -64,6 +66,7 @@ class CITypeManager(object): | ||||
|     """ | ||||
|     manage CIType | ||||
|     """ | ||||
|  | ||||
|     cls = CIType | ||||
|  | ||||
|     def __init__(self): | ||||
| @@ -186,6 +189,9 @@ class CITypeManager(object): | ||||
|  | ||||
|         ci_type = cls.check_is_existed(type_id) | ||||
|  | ||||
|         if ci_type.name in BuiltinModelEnum.all() and kwargs.get('name', ci_type.name) != ci_type.name: | ||||
|             return abort(400, ErrFormat.builtin_type_cannot_update_name) | ||||
|  | ||||
|         cls._validate_unique(type_id=type_id, name=kwargs.get('name')) | ||||
|         # cls._validate_unique(type_id=type_id, alias=kwargs.get('alias') or kwargs.get('name')) | ||||
|  | ||||
| @@ -1095,6 +1101,7 @@ class CITypeAttributeGroupManager(object): | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_by_type_id(type_id, need_other=False): | ||||
|         _type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found) | ||||
|         parent_ids = CITypeInheritanceManager.base(type_id) | ||||
|  | ||||
|         groups = [] | ||||
| @@ -1144,6 +1151,12 @@ class CITypeAttributeGroupManager(object): | ||||
|                 if i.attr_id in attr2pos: | ||||
|                     result[attr2pos[i.attr_id][0]]['attributes'].remove(attr2pos[i.attr_id][1]) | ||||
|  | ||||
|                 if (_type.name in SysComputedAttributes.type2attr and | ||||
|                         attr['name'] in SysComputedAttributes.type2attr[_type.name]): | ||||
|                     attr['sys_computed'] = True | ||||
|                 else: | ||||
|                     attr['sys_computed'] = False | ||||
|  | ||||
|                 attr2pos[i.attr_id] = [group_pos, attr] | ||||
|  | ||||
|             group.pop('inherited_from', None) | ||||
|   | ||||
| @@ -118,6 +118,12 @@ class RelationSourceEnum(BaseEnum): | ||||
|     AUTO_DISCOVERY = "1" | ||||
|  | ||||
|  | ||||
| class BuiltinModelEnum(BaseEnum): | ||||
|     IPAM_SUBNET = "ipam_subnet" | ||||
|     IPAM_ADDRESS = "ipam_address" | ||||
|     IPAM_SCOPE = "ipam_scope" | ||||
|  | ||||
|  | ||||
| BUILTIN_ATTRIBUTES = { | ||||
|     "_updated_at": _l("Update Time"), | ||||
|     "_updated_by": _l("Updated By"), | ||||
| @@ -130,5 +136,18 @@ REDIS_PREFIX_CI_RELATION2 = "CMDB_CI_RELATION2" | ||||
|  | ||||
| BUILTIN_KEYWORDS = {'id', '_id', 'ci_id', 'type', '_type', 'ci_type', 'ticket_id', *BUILTIN_ATTRIBUTES.keys()} | ||||
|  | ||||
|  | ||||
| class SysComputedAttributes(object): | ||||
|     from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes | ||||
|     type2attr = { | ||||
|         BuiltinModelEnum.IPAM_SUBNET: { | ||||
|             SubnetBuiltinAttributes.HOSTS_COUNT, | ||||
|             SubnetBuiltinAttributes.ASSIGN_COUNT, | ||||
|             SubnetBuiltinAttributes.USED_COUNT, | ||||
|             SubnetBuiltinAttributes.FREE_COUNT | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| L_TYPE = None | ||||
| L_CI = None | ||||
|   | ||||
							
								
								
									
										1
									
								
								cmdb-api/api/lib/cmdb/ipam/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								cmdb-api/api/lib/cmdb/ipam/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
							
								
								
									
										137
									
								
								cmdb-api/api/lib/cmdb/ipam/address.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								cmdb-api/api/lib/cmdb/ipam/address.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| import redis_lock | ||||
| from flask import abort | ||||
|  | ||||
| from api.extensions import db | ||||
| from api.extensions import rd | ||||
| from api.lib.cmdb.cache import CITypeCache | ||||
| from api.lib.cmdb.ci import CIManager | ||||
| from api.lib.cmdb.ci import CIRelationManager | ||||
| from api.lib.cmdb.const import BuiltinModelEnum | ||||
| from api.lib.cmdb.ipam.const import IPAddressAssignStatus | ||||
| from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes | ||||
| from api.lib.cmdb.ipam.const import OperateTypeEnum | ||||
| from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes | ||||
| from api.lib.cmdb.ipam.history import OperateHistoryManager | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB | ||||
| from api.lib.cmdb.search.ci_relation.search import Search as RelationSearch | ||||
|  | ||||
|  | ||||
| class IpAddressManager(object): | ||||
|     def __init__(self): | ||||
|         self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) | ||||
|         not self.ci_type and abort(400, ErrFormat.ipam_address_model_not_found.format( | ||||
|             BuiltinModelEnum.IPAM_ADDRESS)) | ||||
|  | ||||
|         self.type_id = self.ci_type.id | ||||
|  | ||||
|     @staticmethod | ||||
|     def list_ip_address(parent_id): | ||||
|         numfound, _, result = CIRelationManager.get_second_cis(parent_id, per_page="all") | ||||
|  | ||||
|         return numfound, result | ||||
|  | ||||
|     def _get_cis(self, ips): | ||||
|         response, _, _, _, _, _ = SearchFromDB( | ||||
|             "_type:{},{}:({})".format(self.type_id, IPAddressBuiltinAttributes.IP, ";".join(ips or [])), | ||||
|             count=10000000, parent_node_perm_passed=True).search() | ||||
|  | ||||
|         return response | ||||
|  | ||||
|     @staticmethod | ||||
|     def _add_relation(parent_id, child_id): | ||||
|         if not parent_id or not child_id: | ||||
|             return | ||||
|  | ||||
|         CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False) | ||||
|  | ||||
|     @staticmethod | ||||
|     def calc_free_count(subnet_id): | ||||
|         db.session.commit() | ||||
|         q = "{}:(0;2),-{}:true".format(IPAddressBuiltinAttributes.ASSIGN_STATUS, IPAddressBuiltinAttributes.IS_USED) | ||||
|  | ||||
|         return len(set(RelationSearch([subnet_id], level=[1], query=q).search(only_ids=True) or [])) | ||||
|  | ||||
|     def _update_subnet_count(self, subnet_id, assign_count, used_count=None): | ||||
|         payload = {} | ||||
|  | ||||
|         cur = CIManager.get_ci_by_id(subnet_id, need_children=False) | ||||
|         if assign_count is not None: | ||||
|             payload[SubnetBuiltinAttributes.ASSIGN_COUNT] = (cur.get( | ||||
|                 SubnetBuiltinAttributes.ASSIGN_COUNT) or 0) + assign_count | ||||
|  | ||||
|         if used_count is not None: | ||||
|             payload[SubnetBuiltinAttributes.USED_COUNT] = used_count | ||||
|  | ||||
|         payload[SubnetBuiltinAttributes.FREE_COUNT] = (cur[SubnetBuiltinAttributes.HOSTS_COUNT] - | ||||
|                                                        self.calc_free_count(subnet_id)) | ||||
|         CIManager().update(subnet_id, **payload) | ||||
|  | ||||
|     def assign_ips(self, ips, subnet_id, cidr, **kwargs): | ||||
|         """ | ||||
|  | ||||
|         :param ips: ip list | ||||
|         :param subnet_id: subnet id | ||||
|         :param cidr: subnet cidr | ||||
|         :param kwargs: other attributes for ip address | ||||
|         :return: | ||||
|         """ | ||||
|         if subnet_id is not None: | ||||
|             subnet = CIManager.get_ci_by_id(subnet_id) | ||||
|         else: | ||||
|             cis, _, _, _, _, _ = SearchFromDB("_type:{},{}:{}".format( | ||||
|                 BuiltinModelEnum.IPAM_SUBNET, SubnetBuiltinAttributes.CIDR, cidr), | ||||
|                 parent_node_perm_passed=True).search() | ||||
|             if cis: | ||||
|                 subnet = cis[0] | ||||
|                 subnet_id = subnet['_id'] | ||||
|             else: | ||||
|                 return abort(400, ErrFormat.ipam_address_model_not_found) | ||||
|  | ||||
|         with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id))): | ||||
|             cis = self._get_cis(ips) | ||||
|             ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis} | ||||
|  | ||||
|             ci_ids = [] | ||||
|             status_change_num = 0 | ||||
|             for ip in ips: | ||||
|                 kwargs['name'] = ip | ||||
|                 kwargs[IPAddressBuiltinAttributes.IP] = ip | ||||
|                 if ip not in ip2ci: | ||||
|                     ci_id = CIManager.add(self.type_id, _sync=True, **kwargs) | ||||
|                     status_change_num += 1 | ||||
|                 else: | ||||
|                     ci_id = ip2ci[ip]['_id'] | ||||
|                     CIManager().update(ci_id, _sync=True, **kwargs) | ||||
|                     if IPAddressBuiltinAttributes.ASSIGN_STATUS in kwargs and ( | ||||
|                             kwargs[IPAddressBuiltinAttributes.ASSIGN_STATUS] != | ||||
|                             ip2ci[ip].get(IPAddressBuiltinAttributes.ASSIGN_STATUS)): | ||||
|                         status_change_num += 1 | ||||
|                 ci_ids.append(ci_id) | ||||
|  | ||||
|                 self._add_relation(subnet_id, ci_id) | ||||
|  | ||||
|             if ips and IPAddressBuiltinAttributes.ASSIGN_STATUS in kwargs: | ||||
|                 self._update_subnet_count(subnet_id, -status_change_num if kwargs.get( | ||||
|                     IPAddressBuiltinAttributes.ASSIGN_STATUS) == IPAddressAssignStatus.UNASSIGNED else status_change_num) | ||||
|  | ||||
|             if ips and IPAddressBuiltinAttributes.IS_USED in kwargs: | ||||
|                 q = "{}:true".format(IPAddressBuiltinAttributes.IS_USED) | ||||
|                 cur_used_ids = RelationSearch([subnet_id], level=[1], query=q).search(only_ids=True) | ||||
|                 for _id in set(cur_used_ids) - set(ci_ids): | ||||
|                     CIManager().update(_id, _sync=True, **{IPAddressBuiltinAttributes.IS_USED: False}) | ||||
|  | ||||
|                 self._update_subnet_count(subnet_id, None, used_count=len(ips)) | ||||
|  | ||||
|         if kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) in ( | ||||
|                 IPAddressAssignStatus.ASSIGNED, IPAddressAssignStatus.RESERVED): | ||||
|             OperateHistoryManager().add(operate_type=OperateTypeEnum.ASSIGN_ADDRESS, | ||||
|                                         cidr=subnet.get(SubnetBuiltinAttributes.CIDR), | ||||
|                                         description=" | ".join(ips)) | ||||
|  | ||||
|         elif kwargs.get(IPAddressBuiltinAttributes.ASSIGN_STATUS) == IPAddressAssignStatus.UNASSIGNED: | ||||
|             OperateHistoryManager().add(operate_type=OperateTypeEnum.REVOKE_ADDRESS, | ||||
|                                         cidr=subnet.get(SubnetBuiltinAttributes.CIDR), | ||||
|                                         description=" | ".join(ips)) | ||||
							
								
								
									
										35
									
								
								cmdb-api/api/lib/cmdb/ipam/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								cmdb-api/api/lib/cmdb/ipam/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from api.lib.utils import BaseEnum | ||||
|  | ||||
|  | ||||
| class IPAddressAssignStatus(BaseEnum): | ||||
|     ASSIGNED = 0 | ||||
|     UNASSIGNED = 1 | ||||
|     RESERVED = 2 | ||||
|  | ||||
|  | ||||
| class OperateTypeEnum(BaseEnum): | ||||
|     ADD_SCOPE = "0" | ||||
|     UPDATE_SCOPE = "1" | ||||
|     DELETE_SCOPE = "2" | ||||
|     ADD_SUBNET = "3" | ||||
|     UPDATE_SUBNET = "4" | ||||
|     DELETE_SUBNET = "5" | ||||
|     ASSIGN_ADDRESS = "6" | ||||
|     REVOKE_ADDRESS = "7" | ||||
|  | ||||
|  | ||||
| class SubnetBuiltinAttributes(BaseEnum): | ||||
|     NAME = 'name' | ||||
|     CIDR = 'cidr' | ||||
|     HOSTS_COUNT = 'hosts_count' | ||||
|     ASSIGN_COUNT = 'assign_count' | ||||
|     USED_COUNT = 'used_count' | ||||
|     FREE_COUNT = 'free_count' | ||||
|  | ||||
|  | ||||
| class IPAddressBuiltinAttributes(BaseEnum): | ||||
|     IP = 'ip' | ||||
|     ASSIGN_STATUS = 'assign_status'  # enum: 0 - assigned   1 - unassigned   2 - reserved | ||||
|     IS_USED = 'is_used'  # bool | ||||
							
								
								
									
										57
									
								
								cmdb-api/api/lib/cmdb/ipam/history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								cmdb-api/api/lib/cmdb/ipam/history.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from flask_login import current_user | ||||
|  | ||||
| from api.lib.cmdb.ipam.const import IPAddressBuiltinAttributes | ||||
| from api.lib.mixin import DBMixin | ||||
| from api.models.cmdb import IPAMOperationHistory | ||||
| from api.models.cmdb import IPAMSubnetScan | ||||
| from api.models.cmdb import IPAMSubnetScanHistory | ||||
|  | ||||
|  | ||||
| class OperateHistoryManager(DBMixin): | ||||
|     cls = IPAMOperationHistory | ||||
|  | ||||
|     def _can_add(self, **kwargs): | ||||
|         kwargs['uid'] = current_user.uid | ||||
|  | ||||
|         return kwargs | ||||
|  | ||||
|     def _can_update(self, **kwargs): | ||||
|         pass | ||||
|  | ||||
|     def _can_delete(self, **kwargs): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class ScanHistoryManager(DBMixin): | ||||
|     cls = IPAMSubnetScanHistory | ||||
|  | ||||
|     def _can_add(self, **kwargs): | ||||
|         return kwargs | ||||
|  | ||||
|     def add(self, **kwargs): | ||||
|         kwargs.pop('_key', None) | ||||
|         kwargs.pop('_secret', None) | ||||
|         ci_id = kwargs.pop('ci_id', None) | ||||
|  | ||||
|         existed = self.cls.get_by(exec_id=kwargs['exec_id'], first=True, to_dict=False) | ||||
|         if existed is None: | ||||
|             self.cls.create(**kwargs) | ||||
|         else: | ||||
|             existed.update(**kwargs) | ||||
|  | ||||
|         if kwargs.get('ips'): | ||||
|             from api.lib.cmdb.ipam.address import IpAddressManager | ||||
|             IpAddressManager().assign_ips(kwargs['ips'], None, kwargs.get('cidr'), | ||||
|                                           **{IPAddressBuiltinAttributes.IS_USED: 1}) | ||||
|  | ||||
|             scan_rule = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False) | ||||
|             if scan_rule is not None: | ||||
|                 scan_rule.update(last_scan_time=kwargs.get('start_at')) | ||||
|  | ||||
|     def _can_update(self, **kwargs): | ||||
|         pass | ||||
|  | ||||
|     def _can_delete(self, **kwargs): | ||||
|         pass | ||||
							
								
								
									
										104
									
								
								cmdb-api/api/lib/cmdb/ipam/stats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								cmdb-api/api/lib/cmdb/ipam/stats.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
|  | ||||
| import json | ||||
| from flask import abort | ||||
|  | ||||
| from api.extensions import rd | ||||
| from api.lib.cmdb.cache import CITypeCache | ||||
| from api.lib.cmdb.ci import CIManager | ||||
| from api.lib.cmdb.const import BuiltinModelEnum | ||||
| from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB | ||||
| from api.models.cmdb import CI | ||||
| from api.models.cmdb import CIRelation | ||||
| from api.models.cmdb import IPAMSubnetScan | ||||
|  | ||||
|  | ||||
| class Stats(object): | ||||
|     def __init__(self): | ||||
|         self.address_type = CITypeCache.get(BuiltinModelEnum.IPAM_ADDRESS) | ||||
|         not self.address_type and abort(400, ErrFormat.ipam_address_model_not_found.format( | ||||
|             BuiltinModelEnum.IPAM_ADDRESS)) | ||||
|  | ||||
|         self.address_type_id = self.address_type.id | ||||
|  | ||||
|         self.subnet_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) | ||||
|         not self.subnet_type and abort(400, ErrFormat.ipam_address_model_not_found.format( | ||||
|             BuiltinModelEnum.IPAM_ADDRESS)) | ||||
|  | ||||
|         self.subnet_type_id = self.subnet_type.id | ||||
|  | ||||
|     def leaf_nodes(self, parent_id): | ||||
|         if str(parent_id) == '0':  # all | ||||
|             ci_ids = [i.id for i in CI.get_by(type_id=self.subnet_type_id, to_dict=False)] | ||||
|             has_children_ci_ids = [i.first_ci_id for i in CIRelation.get_by( | ||||
|                 only_query=True).join(CI, CIRelation.second_ci_id == CI.id).filter( | ||||
|                 CIRelation.first_ci_id.in_(ci_ids)).filter(CI.type_id == self.subnet_type_id)] | ||||
|  | ||||
|             return list(set(ci_ids) - set(has_children_ci_ids)) | ||||
|  | ||||
|         else: | ||||
|             type_id = CIManager().get_by_id(parent_id).type_id | ||||
|             key = [(str(parent_id), type_id)] | ||||
|             result = [] | ||||
|             while True: | ||||
|                 res = [json.loads(x).items() for x in [i or '{}' for i in rd.get( | ||||
|                     [i[0] for i in key], REDIS_PREFIX_CI_RELATION) or []]] | ||||
|  | ||||
|                 for idx, i in enumerate(res): | ||||
|                     if (not i or list(i)[0][1] == self.address_type_id) and key[idx][1] == self.subnet_type_id: | ||||
|                         result.append(int(key[idx][0])) | ||||
|  | ||||
|                 res = [j for i in res for j in i]  # [(id, type_id)] | ||||
|  | ||||
|                 if not res: | ||||
|                     return result | ||||
|  | ||||
|                 key = res | ||||
|  | ||||
|     def statistic_subnets(self, subnet_ids): | ||||
|         if subnet_ids: | ||||
|             response, _, _, _, _, _ = SearchFromDB( | ||||
|                 "_type:{}".format(self.subnet_type_id), | ||||
|                 ci_ids=subnet_ids, | ||||
|                 count=1000000, | ||||
|                 parent_node_perm_passed=True, | ||||
|             ).search() | ||||
|         else: | ||||
|             response = [] | ||||
|  | ||||
|         scans = IPAMSubnetScan.get_by(only_query=True).filter(IPAMSubnetScan.ci_id.in_(list(map(int, subnet_ids)))) | ||||
|         id2scan = {i.ci_id: i for i in scans} | ||||
|  | ||||
|         address_num, address_free_num, address_assign_num, address_used_num = 0, 0, 0, 0 | ||||
|         for subnet in response: | ||||
|             address_num += (subnet.get('hosts_count') or 0) | ||||
|             address_free_num += (subnet.get('free_count') or 0) | ||||
|             address_assign_num += (subnet.get('assign_count') or 0) | ||||
|             address_used_num += (subnet.get('used_count') or 0) | ||||
|  | ||||
|             if id2scan.get(subnet['_id']): | ||||
|                 subnet['scan_enabled'] = id2scan[subnet['_id']].scan_enabled | ||||
|                 subnet['last_scan_time'] = id2scan[subnet['_id']].last_scan_time | ||||
|             else: | ||||
|                 subnet['scan_enabled'] = False | ||||
|                 subnet['last_scan_time'] = None | ||||
|  | ||||
|         return response, address_num, address_free_num, address_assign_num, address_used_num | ||||
|  | ||||
|     def summary(self, parent_id): | ||||
|         subnet_ids = self.leaf_nodes(parent_id) | ||||
|  | ||||
|         subnets, address_num, address_free_num, address_assign_num, address_used_num = ( | ||||
|             self.statistic_subnets(subnet_ids)) | ||||
|  | ||||
|         return dict(subnet_num=len(subnets), | ||||
|                     address_num=address_num, | ||||
|                     address_free_num=address_free_num, | ||||
|                     address_assign_num=address_assign_num, | ||||
|                     address_unassign_num=address_num - address_assign_num, | ||||
|                     address_used_num=address_used_num, | ||||
|                     address_used_free_num=address_num - address_used_num, | ||||
|                     subnets=subnets) | ||||
							
								
								
									
										344
									
								
								cmdb-api/api/lib/cmdb/ipam/subnet.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								cmdb-api/api/lib/cmdb/ipam/subnet.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,344 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from collections import defaultdict | ||||
|  | ||||
| import ipaddress | ||||
| from flask import abort | ||||
|  | ||||
| from api.lib.cmdb.cache import AttributeCache | ||||
| from api.lib.cmdb.cache import CITypeCache | ||||
| from api.lib.cmdb.ci import CIManager | ||||
| from api.lib.cmdb.ci import CIRelationManager | ||||
| from api.lib.cmdb.const import BuiltinModelEnum, BUILTIN_ATTRIBUTES | ||||
| from api.lib.cmdb.ipam.const import OperateTypeEnum | ||||
| from api.lib.cmdb.ipam.const import SubnetBuiltinAttributes | ||||
| from api.lib.cmdb.ipam.history import OperateHistoryManager | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB | ||||
| from api.models.cmdb import CI | ||||
| from api.models.cmdb import CIRelation | ||||
| from api.models.cmdb import IPAMSubnetScan | ||||
|  | ||||
|  | ||||
| class SubnetManager(object): | ||||
|     def __init__(self): | ||||
|         self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SUBNET) | ||||
|         not self.ci_type and abort(400, ErrFormat.ipam_subnet_model_not_found.format( | ||||
|             BuiltinModelEnum.IPAM_SUBNET)) | ||||
|  | ||||
|         self.type_id = self.ci_type.id | ||||
|  | ||||
|     def scan_rules(self, oneagent_id, last_update_at=None): | ||||
|         result = [] | ||||
|         rules = IPAMSubnetScan.get_by(agent_id=oneagent_id, to_dict=True) | ||||
|         ci_ids = [i['ci_id'] for i in rules] | ||||
|         if ci_ids: | ||||
|             response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id), | ||||
|                                                    ci_ids=list(ci_ids), | ||||
|                                                    count=1000000, | ||||
|                                                    fl=[SubnetBuiltinAttributes.CIDR], | ||||
|                                                    parent_node_perm_passed=True).search() | ||||
|             id2ci = {i['_id']: i for i in response} | ||||
|  | ||||
|             for rule in rules: | ||||
|                 if rule['ci_id'] in id2ci: | ||||
|                     rule[SubnetBuiltinAttributes.CIDR] = id2ci[rule['ci_id']][SubnetBuiltinAttributes.CIDR] | ||||
|                     result.append(rule) | ||||
|  | ||||
|         new_last_update_at = "" | ||||
|         for i in result: | ||||
|             __last_update_at = max([i['updated_at'] or "", i['created_at'] or ""]) | ||||
|             if new_last_update_at < __last_update_at: | ||||
|                 new_last_update_at = __last_update_at | ||||
|  | ||||
|         if not last_update_at or new_last_update_at > last_update_at: | ||||
|             return result, new_last_update_at | ||||
|         else: | ||||
|             return [], new_last_update_at | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_hosts(cidr): | ||||
|         try: | ||||
|             return list(map(str, ipaddress.ip_network(cidr).hosts())) | ||||
|         except ValueError: | ||||
|             return [] | ||||
|  | ||||
|     def get_by_id(self, subnet_id): | ||||
|         response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id), | ||||
|                                                ci_ids=[subnet_id], | ||||
|                                                parent_node_perm_passed=True).search() | ||||
|         scan_rule = IPAMSubnetScan.get_by(ci_id=subnet_id, first=True, to_dict=True) | ||||
|         if scan_rule and response: | ||||
|             scan_rule.update(response[0]) | ||||
|  | ||||
|         return scan_rule | ||||
|  | ||||
|     def tree_view(self): | ||||
|         scope = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE) | ||||
|         ci_types = scope and [scope.id, self.type_id] or [self.type_id] | ||||
|  | ||||
|         relations = defaultdict(set) | ||||
|         ids = set() | ||||
|         has_parent_ids = set() | ||||
|         for i in CIRelation.get_by(only_query=True).join( | ||||
|                 CI, CI.id == CIRelation.first_ci_id).filter(CI.type_id.in_(ci_types)): | ||||
|             relations[i.first_ci_id].add(i.second_ci_id) | ||||
|             ids.add(i.first_ci_id) | ||||
|             ids.add(i.second_ci_id) | ||||
|             has_parent_ids.add(i.second_ci_id) | ||||
|         for i in CIRelation.get_by(only_query=True).join( | ||||
|                 CI, CI.id == CIRelation.second_ci_id).filter(CI.type_id.in_(ci_types)): | ||||
|             relations[i.first_ci_id].add(i.second_ci_id) | ||||
|             ids.add(i.first_ci_id) | ||||
|             ids.add(i.second_ci_id) | ||||
|             has_parent_ids.add(i.second_ci_id) | ||||
|  | ||||
|         for i in CI.get_by(only_query=True).filter(CI.type_id.in_(ci_types)): | ||||
|             ids.add(i.id) | ||||
|  | ||||
|         for _id in ids: | ||||
|             if _id not in has_parent_ids: | ||||
|                 relations[None].add(_id) | ||||
|  | ||||
|         type2name = dict() | ||||
|         type2name[self.type_id] = AttributeCache.get(self.ci_type.show_id or self.ci_type.unique_id).name | ||||
|  | ||||
|         fl = [type2name[self.type_id]] | ||||
|         if scope: | ||||
|             type2name[scope.id] = AttributeCache.get(scope.show_id or scope.unique_id).name | ||||
|             fl.append(type2name[scope.id]) | ||||
|  | ||||
|         response, _, _, _, _, _ = SearchFromDB("_type:({})".format(";".join(map(str, ci_types))), | ||||
|                                                ci_ids=list(ids), | ||||
|                                                count=1000000, | ||||
|                                                fl=list(set(fl + [SubnetBuiltinAttributes.CIDR])), | ||||
|                                                parent_node_perm_passed=True).search() | ||||
|         id2ci = {i['_id']: i for i in response} | ||||
|  | ||||
|         def _build_tree(_tree, parent_id=None): | ||||
|             tree = [] | ||||
|             for child_id in _tree.get(parent_id, []): | ||||
|                 children = sorted(_build_tree(_tree, child_id), key=lambda x: x['_id']) | ||||
|                 if not id2ci.get(child_id): | ||||
|                     continue | ||||
|                 tree.append({'children': children, **id2ci[child_id]}) | ||||
|             return tree | ||||
|  | ||||
|         result = sorted(_build_tree(relations), key=lambda x: x['_id']) | ||||
|  | ||||
|         return result, type2name | ||||
|  | ||||
|     @staticmethod | ||||
|     def _is_valid_cidr(cidr): | ||||
|         try: | ||||
|             return str(ipaddress.ip_network(cidr)) | ||||
|         except ValueError: | ||||
|             return abort(400, ErrFormat.ipam_cidr_invalid_notation.format(cidr)) | ||||
|  | ||||
|     def _check_root_node_is_overlapping(self, cidr, _id=None): | ||||
|         none_root_nodes = [i.id for i in CI.get_by(only_query=True).join( | ||||
|             CIRelation, CIRelation.second_ci_id == CI.id).filter(CI.type_id == self.type_id)] | ||||
|         all_nodes = [i.id for i in CI.get_by(type_id=self.type_id, to_dict=False, fl=['id'])] | ||||
|  | ||||
|         root_nodes = set(all_nodes) - set(none_root_nodes) - set(_id and [_id] or []) | ||||
|         response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id), | ||||
|                                                ci_ids=list(root_nodes), | ||||
|                                                parent_node_perm_passed=True).search() | ||||
|  | ||||
|         cur_subnet = ipaddress.ip_network(cidr) | ||||
|         for item in response: | ||||
|             if item['_id'] == _id: | ||||
|                 continue | ||||
|  | ||||
|             if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))): | ||||
|                 return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR))) | ||||
|  | ||||
|         return cidr | ||||
|  | ||||
|     def _check_child_node_is_overlapping(self, parent_id, cidr, _id=None): | ||||
|         child_nodes = [i.second_ci_id for i in CIRelation.get_by( | ||||
|             first_ci_id=parent_id, to_dict=False, fl=['second_ci_id']) if i.second_ci_id != _id] | ||||
|         if not child_nodes: | ||||
|             return | ||||
|  | ||||
|         response, _, _, _, _, _ = SearchFromDB("_type:{}".format(self.type_id), | ||||
|                                                ci_ids=list(child_nodes), | ||||
|                                                parent_node_perm_passed=True).search() | ||||
|  | ||||
|         cur_subnet = ipaddress.ip_network(cidr) | ||||
|         for item in response: | ||||
|             if item['_id'] == _id: | ||||
|                 continue | ||||
|  | ||||
|             if cur_subnet.overlaps(ipaddress.ip_network(item.get(SubnetBuiltinAttributes.CIDR))): | ||||
|                 return abort(400, ErrFormat.ipam_subnet_overlapped.format(cidr, item.get(SubnetBuiltinAttributes.CIDR))) | ||||
|  | ||||
|     def validate_cidr(self, parent_id, cidr, _id=None): | ||||
|         cidr = self._is_valid_cidr(cidr) | ||||
|  | ||||
|         if not parent_id: | ||||
|             return self._check_root_node_is_overlapping(cidr, _id) | ||||
|  | ||||
|         parent_subnet = CIManager().get_ci_by_id(parent_id, need_children=False) | ||||
|         if parent_subnet['ci_type'] == BuiltinModelEnum.IPAM_SUBNET: | ||||
|             if parent_subnet.get(SubnetBuiltinAttributes.CIDR): | ||||
|                 prefix = int(cidr.split('/')[1]) | ||||
|                 if int(parent_subnet[SubnetBuiltinAttributes.CIDR].split('/')[1]) >= prefix: | ||||
|                     return abort(400, ErrFormat.ipam_subnet_prefix_length_invalid.format(prefix)) | ||||
|  | ||||
|                 valid_subnets = [str(i) for i in | ||||
|                                  ipaddress.ip_network(parent_subnet[SubnetBuiltinAttributes.CIDR]).subnets( | ||||
|                                      new_prefix=prefix)] | ||||
|                 if cidr not in valid_subnets: | ||||
|                     return abort(400, ErrFormat.ipam_cidr_invalid_subnet.format(cidr, valid_subnets)) | ||||
|             else: | ||||
|                 return abort(400, ErrFormat.ipam_parent_subnet_node_cidr_cannot_empty) | ||||
|  | ||||
|         self._check_child_node_is_overlapping(parent_id, cidr, _id) | ||||
|  | ||||
|         return cidr | ||||
|  | ||||
|     def _add_subnet(self, cidr, **kwargs): | ||||
|         kwargs[SubnetBuiltinAttributes.HOSTS_COUNT] = ipaddress.ip_network(cidr).num_addresses - 2 | ||||
|         kwargs[SubnetBuiltinAttributes.USED_COUNT] = 0 | ||||
|         kwargs[SubnetBuiltinAttributes.ASSIGN_COUNT] = 0 | ||||
|         kwargs[SubnetBuiltinAttributes.FREE_COUNT] = kwargs[SubnetBuiltinAttributes.HOSTS_COUNT] | ||||
|  | ||||
|         return CIManager().add(self.type_id, cidr=cidr, **kwargs) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _add_scan_rule(ci_id, agent_id, cron, scan_enabled=True): | ||||
|         IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _add_relation(parent_id, child_id): | ||||
|         if not parent_id or not child_id: | ||||
|             return | ||||
|  | ||||
|         CIRelationManager().add(parent_id, child_id, valid=False) | ||||
|  | ||||
|     def add(self, cidr, parent_id, agent_id, cron, scan_enabled=True, **kwargs): | ||||
|         cidr = self.validate_cidr(parent_id, cidr) | ||||
|  | ||||
|         ci_id = self._add_subnet(cidr, **kwargs) | ||||
|  | ||||
|         self._add_scan_rule(ci_id, agent_id, cron, scan_enabled) | ||||
|  | ||||
|         self._add_relation(parent_id, ci_id) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SUBNET, | ||||
|                                     cidr=cidr, | ||||
|                                     description=cidr) | ||||
|  | ||||
|         return ci_id | ||||
|  | ||||
|     @staticmethod | ||||
|     def _update_subnet(_id, **kwargs): | ||||
|         return CIManager().update(_id, **kwargs) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _update_scan_rule(ci_id, agent_id, cron, scan_enabled=True): | ||||
|         existed = IPAMSubnetScan.get_by(ci_id=ci_id, first=True, to_dict=False) | ||||
|         if existed is not None: | ||||
|             existed.update(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled) | ||||
|         else: | ||||
|             IPAMSubnetScan.create(ci_id=ci_id, agent_id=agent_id, cron=cron, scan_enabled=scan_enabled) | ||||
|  | ||||
|     def update(self, _id, **kwargs): | ||||
|         kwargs[SubnetBuiltinAttributes.CIDR] = self.validate_cidr(kwargs.pop('parent_id', None), | ||||
|                                                                   kwargs.get(SubnetBuiltinAttributes.CIDR), _id) | ||||
|  | ||||
|         agent_id = kwargs.pop('agent_id', None) | ||||
|         cron = kwargs.pop('cron', None) | ||||
|         scan_enabled = kwargs.pop('scan_enabled', True) | ||||
|  | ||||
|         cur = CIManager.get_ci_by_id(_id, need_children=False) | ||||
|  | ||||
|         self._update_subnet(_id, **kwargs) | ||||
|  | ||||
|         self._update_scan_rule(_id, agent_id, cron, scan_enabled) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SUBNET, | ||||
|                                     cidr=cur.get(SubnetBuiltinAttributes.CIDR), | ||||
|                                     description="{} -> {}".format(cur.get(SubnetBuiltinAttributes.CIDR), | ||||
|                                                                   kwargs.get(SubnetBuiltinAttributes.CIDR))) | ||||
|  | ||||
|         return _id | ||||
|  | ||||
|     def delete(self, _id): | ||||
|         if CIRelation.get_by(only_query=True).join(CI, CI.id == CIRelation.second_ci_id).filter( | ||||
|                 CIRelation.first_ci_id == _id).filter(CI.type_id == self.type_id).first(): | ||||
|             return abort(400, ErrFormat.ipam_subnet_cannot_delete) | ||||
|  | ||||
|         existed = IPAMSubnetScan.get_by(ci_id=_id, first=True, to_dict=False) | ||||
|         existed and existed.delete() | ||||
|  | ||||
|         for i in CIRelation.get_by(first_ci_id=_id, to_dict=False): | ||||
|             i.delete() | ||||
|  | ||||
|         cur = CIManager.get_ci_by_id(_id, need_children=False) | ||||
|  | ||||
|         CIManager().delete(_id) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SUBNET, | ||||
|                                     cidr=cur.get(SubnetBuiltinAttributes.CIDR), | ||||
|                                     description=cur.get(SubnetBuiltinAttributes.CIDR)) | ||||
|  | ||||
|         return _id | ||||
|  | ||||
|  | ||||
| class SubnetScopeManager(object): | ||||
|     def __init__(self): | ||||
|         self.ci_type = CITypeCache.get(BuiltinModelEnum.IPAM_SCOPE) | ||||
|         not self.ci_type and abort(400, ErrFormat.ipam_subnet_model_not_found.format( | ||||
|             BuiltinModelEnum.IPAM_SCOPE)) | ||||
|  | ||||
|         self.type_id = self.ci_type.id | ||||
|  | ||||
|     def _add_scope(self, name): | ||||
|         return CIManager().add(self.type_id, name=name) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _add_relation(parent_id, child_id): | ||||
|         if not parent_id or not child_id: | ||||
|             return | ||||
|  | ||||
|         CIRelationManager().add(parent_id, child_id, valid=False) | ||||
|  | ||||
|     def add(self, parent_id, name): | ||||
|         ci_id = self._add_scope(name) | ||||
|  | ||||
|         self._add_relation(parent_id, ci_id) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_SCOPE, | ||||
|                                     description=name) | ||||
|  | ||||
|         return ci_id | ||||
|  | ||||
|     @staticmethod | ||||
|     def _update_scope(_id, name): | ||||
|         return CIManager().update(_id, name=name) | ||||
|  | ||||
|     def update(self, _id, name): | ||||
|         cur = CIManager.get_ci_by_id(_id, need_children=False) | ||||
|  | ||||
|         res = self._update_scope(_id, name) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.UPDATE_SCOPE, | ||||
|                                     description="{} -> {}".format(cur.get('name'), name)) | ||||
|  | ||||
|         return res | ||||
|  | ||||
|     @staticmethod | ||||
|     def delete(_id): | ||||
|         if CIRelation.get_by(first_ci_id=_id, first=True, to_dict=False): | ||||
|             return abort(400, ErrFormat.ipam_scope_cannot_delete) | ||||
|  | ||||
|         cur = CIManager.get_ci_by_id(_id, need_children=False) | ||||
|  | ||||
|         CIManager().delete(_id) | ||||
|  | ||||
|         OperateHistoryManager().add(operate_type=OperateTypeEnum.DELETE_SCOPE, | ||||
|                                     description=cur.get('name')) | ||||
|  | ||||
|         return _id | ||||
| @@ -1,7 +1,6 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
| import copy | ||||
| import functools | ||||
|  | ||||
| import redis_lock | ||||
| from flask import abort | ||||
| from flask import current_app | ||||
| @@ -10,6 +9,7 @@ from flask_login import current_user | ||||
|  | ||||
| from api.extensions import db | ||||
| from api.extensions import rd | ||||
| from api.lib.cmdb.const import BUILTIN_ATTRIBUTES | ||||
| from api.lib.cmdb.const import ResourceTypeEnum | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.mixin import DBMixin | ||||
| @@ -27,7 +27,7 @@ class CIFilterPermsCRUD(DBMixin): | ||||
|         result = {} | ||||
|         for i in res: | ||||
|             if i['attr_filter']: | ||||
|                 i['attr_filter'] = i['attr_filter'].split(',') | ||||
|                 i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys()) | ||||
|  | ||||
|             if i['rid'] not in result: | ||||
|                 result[i['rid']] = i | ||||
| @@ -62,7 +62,7 @@ class CIFilterPermsCRUD(DBMixin): | ||||
|         result = {} | ||||
|         for i in res: | ||||
|             if i['attr_filter']: | ||||
|                 i['attr_filter'] = i['attr_filter'].split(',') | ||||
|                 i['attr_filter'] = i['attr_filter'].split(',') + list(BUILTIN_ATTRIBUTES.keys()) | ||||
|  | ||||
|             if i['type_id'] not in result: | ||||
|                 result[i['type_id']] = i | ||||
|   | ||||
| @@ -21,6 +21,7 @@ from api.lib.cmdb.const import ConstraintEnum | ||||
| from api.lib.cmdb.const import PermEnum | ||||
| from api.lib.cmdb.const import ResourceTypeEnum | ||||
| from api.lib.cmdb.const import RoleEnum | ||||
| from api.lib.cmdb.const import SysComputedAttributes | ||||
| from api.lib.cmdb.perms import CIFilterPermsCRUD | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.exception import AbortException | ||||
| @@ -48,7 +49,7 @@ class PreferenceManager(object): | ||||
|         type2group = {} | ||||
|         for i in db.session.query(CITypeGroupItem, CITypeGroup).join( | ||||
|                 CITypeGroup, CITypeGroup.id == CITypeGroupItem.group_id).filter( | ||||
|             CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)): | ||||
|                 CITypeGroup.deleted.is_(False)).filter(CITypeGroupItem.deleted.is_(False)): | ||||
|             type2group[i.CITypeGroupItem.type_id] = i.CITypeGroup.to_dict() | ||||
|  | ||||
|         types = db.session.query(PreferenceShowAttributes.type_id).filter( | ||||
| @@ -132,17 +133,13 @@ class PreferenceManager(object): | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_show_attributes(type_id): | ||||
|         _type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found) | ||||
|         type_id = _type and _type.id | ||||
|  | ||||
|         if not isinstance(type_id, six.integer_types): | ||||
|             _type = CITypeCache.get(type_id) | ||||
|             type_id = _type and _type.id | ||||
|  | ||||
|         # attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join( | ||||
|         #     CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter( | ||||
|         #     PreferenceShowAttributes.uid == current_user.uid).filter( | ||||
|         #     PreferenceShowAttributes.type_id == type_id).filter( | ||||
|         #     PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).group_by( | ||||
|         #     CITypeAttribute.attr_id).all() | ||||
|  | ||||
|         attrs = PreferenceShowAttributes.get_by(uid=current_user.uid, type_id=type_id, to_dict=False) | ||||
|  | ||||
|         result = [] | ||||
| @@ -173,6 +170,12 @@ class PreferenceManager(object): | ||||
|                 i.update(dict(choice_value=AttributeManager.get_choice_values( | ||||
|                     i["id"], i["value_type"], i.get("choice_web_hook"), i.get("choice_other")))) | ||||
|  | ||||
|                 if (_type.name in SysComputedAttributes.type2attr and | ||||
|                         i['name'] in SysComputedAttributes.type2attr[_type.name]): | ||||
|                     i['sys_computed'] = True | ||||
|                 else: | ||||
|                     i['sys_computed'] = False | ||||
|  | ||||
|         return is_subscribed, result | ||||
|  | ||||
|     @classmethod | ||||
|   | ||||
| @@ -155,4 +155,17 @@ class ErrFormat(CommonErrFormat): | ||||
|     # 因为该分组下定义了拓扑视图,不能删除 | ||||
|     topo_view_exists_cannot_delete_group = _l("The group cannot be deleted because the topology view already exists") | ||||
|  | ||||
|     relation_path_search_src_target_required = _l("Both the source model and the target model must be selected") | ||||
|     relation_path_search_src_target_required = _l("Both the source model and the target model must be selected") | ||||
|  | ||||
|     builtin_type_cannot_update_name = _l("The names of built-in models cannot be changed") | ||||
|     # # IPAM | ||||
|     ipam_subnet_model_not_found = _l("The subnet model {} does not exist") | ||||
|     ipam_address_model_not_found = _l("The IP Address model {} does not exist") | ||||
|     ipam_cidr_invalid_notation = _l("CIDR {} is an invalid notation") | ||||
|     ipam_cidr_invalid_subnet = _l("Invalid CIDR: {}, available subnets: {}") | ||||
|     ipam_subnet_prefix_length_invalid = _l("Invalid subnet prefix length: {}") | ||||
|     ipam_parent_subnet_node_cidr_cannot_empty = _l("parent node cidr must be required") | ||||
|     ipam_subnet_overlapped = _l("{} and {} overlap") | ||||
|     ipam_subnet_cannot_delete = _l("Cannot delete because child nodes exist") | ||||
|     ipam_subnet_not_found = _l("Subnet is not found") | ||||
|     ipam_scope_cannot_delete = _l("Cannot delete because child nodes exist") | ||||
|   | ||||
| @@ -53,6 +53,7 @@ class CMDBApp(BaseApp): | ||||
|          "perms": ["read", "create_topology_group", "update_topology_group", "delete_topology_group", | ||||
|                    "create_topology_view"], | ||||
|          }, | ||||
|         {"page": "IPAM", "page_cn": "IPAM", "perms": ["read"]}, | ||||
|     ] | ||||
|  | ||||
|     def __init__(self): | ||||
|   | ||||
| @@ -668,3 +668,40 @@ class InnerKV(Model): | ||||
|  | ||||
|     key = db.Column(db.String(128), index=True) | ||||
|     value = db.Column(db.Text) | ||||
|  | ||||
|  | ||||
| class IPAMSubnetScan(Model): | ||||
|     __tablename__ = "c_ipam_subnet_scans" | ||||
|  | ||||
|     ci_id = db.Column(db.Integer, index=True, nullable=False) | ||||
|     scan_enabled = db.Column(db.Boolean, default=True) | ||||
|     last_scan_time = db.Column(db.DateTime) | ||||
|  | ||||
|     # scan rules | ||||
|     agent_id = db.Column(db.String(8), index=True) | ||||
|     cron = db.Column(db.String(128)) | ||||
|  | ||||
|  | ||||
| class IPAMSubnetScanHistory(Model2): | ||||
|     __tablename__ = "c_ipam_subnet_scan_histories" | ||||
|  | ||||
|     subnet_scan_id = db.Column(db.Integer, index=True) | ||||
|     exec_id = db.Column(db.String(64), index=True) | ||||
|     cidr = db.Column(db.String(18), index=True) | ||||
|     start_at = db.Column(db.DateTime) | ||||
|     end_at = db.Column(db.DateTime) | ||||
|     status = db.Column(db.Integer, default=0)  # 0 is ok | ||||
|     stdout = db.Column(db.Text) | ||||
|     ip_num = db.Column(db.Integer) | ||||
|     ips = db.Column(db.JSON)  # keep only the last 10 records | ||||
|  | ||||
|  | ||||
| class IPAMOperationHistory(Model2): | ||||
|     __tablename__ = "c_ipam_operation_histories" | ||||
|  | ||||
|     from api.lib.cmdb.ipam.const import OperateTypeEnum | ||||
|  | ||||
|     uid = db.Column(db.Integer, index=True) | ||||
|     cidr = db.Column(db.String(18), index=True) | ||||
|     operate_type = db.Column(db.Enum(*OperateTypeEnum.all())) | ||||
|     description = db.Column(db.Text) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import datetime | ||||
| import json | ||||
| import redis_lock | ||||
| from flask import current_app | ||||
| from flask import has_request_context | ||||
| from flask_login import login_user | ||||
|  | ||||
| import api.lib.cmdb.ci | ||||
| @@ -53,8 +54,9 @@ def ci_cache(ci_id, operate_type, record_id): | ||||
|     current_app.logger.info("{0} flush..........".format(ci_id)) | ||||
|  | ||||
|     if operate_type: | ||||
|         current_app.test_request_context().push() | ||||
|         login_user(UserCache.get('worker')) | ||||
|         if not has_request_context(): | ||||
|             current_app.test_request_context().push() | ||||
|             login_user(UserCache.get('worker')) | ||||
|  | ||||
|         _, enum_map = CITypeAttributeManager.get_attr_names_label_enum(ci_dict.get('_type')) | ||||
|         payload = dict() | ||||
| @@ -184,8 +186,9 @@ def ci_relation_add(parent_dict, child_id, uid): | ||||
|     from api.lib.cmdb.search import SearchError | ||||
|     from api.lib.cmdb.search.ci import search | ||||
|  | ||||
|     current_app.test_request_context().push() | ||||
|     login_user(UserCache.get(uid)) | ||||
|     if not has_request_context(): | ||||
|         current_app.test_request_context().push() | ||||
|         login_user(UserCache.get(uid)) | ||||
|  | ||||
|     for parent in parent_dict: | ||||
|         parent_ci_type_name, _attr_name = parent.strip()[1:].split('.', 1) | ||||
| @@ -272,8 +275,9 @@ def ci_type_attribute_order_rebuild(type_id, uid): | ||||
| def calc_computed_attribute(attr_id, uid): | ||||
|     from api.lib.cmdb.ci import CIManager | ||||
|  | ||||
|     current_app.test_request_context().push() | ||||
|     login_user(UserCache.get(uid)) | ||||
|     if not has_request_context(): | ||||
|         current_app.test_request_context().push() | ||||
|         login_user(UserCache.get(uid)) | ||||
|  | ||||
|     cim = CIManager() | ||||
|     for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False): | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -7,7 +7,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2024-09-26 17:57+0800\n" | ||||
| "POT-Creation-Date: 2024-11-11 17:40+0800\n" | ||||
| "PO-Revision-Date: 2023-12-25 20:21+0800\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: zh\n" | ||||
| @@ -92,6 +92,14 @@ msgstr "您没有操作权限!" | ||||
| msgid "Only the creator or administrator has permission!" | ||||
| msgstr "只有创建人或者管理员才有权限!" | ||||
|  | ||||
| #: api/lib/cmdb/const.py:128 | ||||
| msgid "Update Time" | ||||
| msgstr "更新时间" | ||||
|  | ||||
| #: api/lib/cmdb/const.py:129 | ||||
| msgid "Updated By" | ||||
| msgstr "更新人" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:9 | ||||
| msgid "CI Model" | ||||
| msgstr "模型配置" | ||||
| @@ -474,11 +482,11 @@ msgstr "{}格式错误,应该为:%Y-%m-%d %H:%M:%S" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:150 | ||||
| msgid "CMDB data reconciliation results" | ||||
| msgstr "" | ||||
| msgstr "CMDB数据合规检查结果" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:151 | ||||
| msgid "Number of {} illegal: {}" | ||||
| msgstr "" | ||||
| msgstr "{} 不合规数: {}" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:153 | ||||
| msgid "Topology view {} already exists" | ||||
| @@ -496,6 +504,46 @@ msgstr "因为该分组下定义了拓扑视图,不能删除" | ||||
| msgid "Both the source model and the target model must be selected" | ||||
| msgstr "源模型和目标模型不能为空!" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:160 | ||||
| msgid "The names of built-in models cannot be changed" | ||||
| msgstr "内置模型的名字不能修改" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:162 | ||||
| msgid "The subnet model {} does not exist" | ||||
| msgstr "子网模型 {} 不存在!" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:163 | ||||
| msgid "The IP Address model {} does not exist" | ||||
| msgstr "IP地址模型 {} 不存在!" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:164 | ||||
| msgid "CIDR {} is an invalid notation" | ||||
| msgstr "CIDR {} 写法不正确!" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:165 | ||||
| msgid "Invalid CIDR: {}, available subnets: {}" | ||||
| msgstr "无效的CIDR: {}, 可用的子网: {}" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:166 | ||||
| msgid "Invalid subnet prefix length: {}" | ||||
| msgstr "无效的子网前缀长度: {}" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:167 | ||||
| msgid "parent node cidr must be required" | ||||
| msgstr "必须要有父节点" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:168 | ||||
| msgid "{} and {} overlap" | ||||
| msgstr "{} 和 {} 有重叠" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:169 api/lib/cmdb/resp_format.py:171 | ||||
| msgid "Cannot delete because child nodes exist" | ||||
| msgstr "因为子节点已经存在,不能删除" | ||||
|  | ||||
| #: api/lib/cmdb/resp_format.py:170 | ||||
| msgid "Subnet is not found" | ||||
| msgstr "子网不存在" | ||||
|  | ||||
| #: api/lib/common_setting/resp_format.py:8 | ||||
| msgid "Company info already existed" | ||||
| msgstr "公司信息已存在,无法创建!" | ||||
|   | ||||
| @@ -24,6 +24,7 @@ from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS | ||||
| from api.lib.cmdb.cache import AttributeCache | ||||
| from api.lib.cmdb.const import PermEnum | ||||
| from api.lib.cmdb.const import ResourceTypeEnum | ||||
| from api.lib.cmdb.ipam.subnet import SubnetManager | ||||
| from api.lib.cmdb.resp_format import ErrFormat | ||||
| from api.lib.cmdb.search import SearchError | ||||
| from api.lib.cmdb.search.ci import search as ci_search | ||||
| @@ -293,9 +294,13 @@ class AutoDiscoveryRuleSyncView(APIView): | ||||
|  | ||||
|                 return self.jsonify(rules=rules, last_update_at=last_update_at) | ||||
|  | ||||
|         rules, last_update_at = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at) | ||||
|         rules, last_update_at1 = AutoDiscoveryCITypeCRUD.get(None, oneagent_id, oneagent_name, last_update_at) | ||||
|  | ||||
|         return self.jsonify(rules=rules, last_update_at=last_update_at) | ||||
|         subnet_scan_rules, last_update_at2 = SubnetManager().scan_rules(oneagent_id, last_update_at) | ||||
|  | ||||
|         return self.jsonify(rules=rules, | ||||
|                             subnet_scan_rules=subnet_scan_rules, | ||||
|                             last_update_at=max(last_update_at1 or "", last_update_at2 or "")) | ||||
|  | ||||
|  | ||||
| class AutoDiscoveryRuleSyncHistoryView(APIView): | ||||
|   | ||||
							
								
								
									
										1
									
								
								cmdb-api/api/views/cmdb/ipam/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								cmdb-api/api/views/cmdb/ipam/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
							
								
								
									
										39
									
								
								cmdb-api/api/views/cmdb/ipam/address.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								cmdb-api/api/views/cmdb/ipam/address.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| from api.lib.cmdb.ipam.address import IpAddressManager | ||||
| 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.utils import handle_arg_list | ||||
| from api.resource import APIView | ||||
|  | ||||
| app_cli = CMDBApp() | ||||
|  | ||||
|  | ||||
| class IPAddressView(APIView): | ||||
|     url_prefix = ("/ipam/address",) | ||||
|  | ||||
|     @args_required("parent_id") | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def get(self): | ||||
|         parent_id = request.args.get("parent_id") | ||||
|  | ||||
|         numfound, result = IpAddressManager.list_ip_address(parent_id) | ||||
|  | ||||
|         return self.jsonify(numfound=numfound, result=result) | ||||
|  | ||||
|     @args_required("ips") | ||||
|     @args_required("assign_status", value_required=False) | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def post(self): | ||||
|         ips = handle_arg_list(request.values.pop("ips")) | ||||
|         parent_id = request.values.pop("parent_id", None) | ||||
|         cidr = request.values.pop("cidr", None) | ||||
|  | ||||
|         IpAddressManager().assign_ips(ips, parent_id, cidr, **request.values) | ||||
|  | ||||
|         return self.jsonify(code=200) | ||||
							
								
								
									
										53
									
								
								cmdb-api/api/views/cmdb/ipam/histories.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								cmdb-api/api/views/cmdb/ipam/histories.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| from api.lib.cmdb.ipam.history import OperateHistoryManager | ||||
| from api.lib.cmdb.ipam.history import ScanHistoryManager | ||||
| 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.utils import get_page | ||||
| from api.lib.utils import get_page_size | ||||
| from api.lib.utils import handle_arg_list | ||||
| from api.resource import APIView | ||||
|  | ||||
| app_cli = CMDBApp() | ||||
|  | ||||
|  | ||||
| class IPAMOperateHistoryView(APIView): | ||||
|     url_prefix = ("/ipam/history/operate",) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def get(self): | ||||
|         page = get_page(request.values.pop("page", 1)) | ||||
|         page_size = get_page_size(request.values.pop("page_size", None)) | ||||
|         operate_type = handle_arg_list(request.values.pop('operate_type', [])) | ||||
|         if operate_type: | ||||
|             request.values["operate_type"] = operate_type | ||||
|  | ||||
|         numfound, result = OperateHistoryManager.search(page, page_size, **request.values) | ||||
|  | ||||
|         return self.jsonify(numfound=numfound, result=result) | ||||
|  | ||||
|  | ||||
| class IPAMScanHistoryView(APIView): | ||||
|     url_prefix = ("/ipam/history/scan",) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def get(self): | ||||
|         page = get_page(request.values.pop("page", 1)) | ||||
|         page_size = get_page_size(request.values.pop("page_size", None)) | ||||
|  | ||||
|         numfound, result = ScanHistoryManager.search(page, page_size, **request.values) | ||||
|  | ||||
|         return self.jsonify(numfound=numfound, result=result) | ||||
|  | ||||
|     @args_required("exec_id") | ||||
|     def post(self): | ||||
|  | ||||
|         ScanHistoryManager().add(**request.values) | ||||
|  | ||||
|         return self.jsonify(code=200) | ||||
							
								
								
									
										24
									
								
								cmdb-api/api/views/cmdb/ipam/ipam_stats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								cmdb-api/api/views/cmdb/ipam/ipam_stats.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| from api.lib.cmdb.ipam.stats import Stats | ||||
| 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.resource import APIView | ||||
|  | ||||
| app_cli = CMDBApp() | ||||
|  | ||||
|  | ||||
| class IPAMStatsView(APIView): | ||||
|     url_prefix = '/ipam/stats' | ||||
|  | ||||
|     @args_required("parent_id") | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def get(self): | ||||
|         parent_id = request.values.get("parent_id") | ||||
|  | ||||
|         return self.jsonify(Stats().summary(parent_id)) | ||||
							
								
								
									
										75
									
								
								cmdb-api/api/views/cmdb/ipam/subnet.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								cmdb-api/api/views/cmdb/ipam/subnet.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| # -*- coding:utf-8 -*- | ||||
|  | ||||
| from flask import request | ||||
|  | ||||
| from api.lib.cmdb.ipam.subnet import SubnetManager | ||||
| from api.lib.cmdb.ipam.subnet import SubnetScopeManager | ||||
| 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.resource import APIView | ||||
|  | ||||
| app_cli = CMDBApp() | ||||
|  | ||||
|  | ||||
| class SubnetView(APIView): | ||||
|     url_prefix = ("/ipam/subnet", "/ipam/subnet/hosts", "/ipam/subnet/<int:_id>") | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def get(self, _id=None): | ||||
|         if "hosts" in request.url: | ||||
|             return self.jsonify(SubnetManager.get_hosts(request.values.get('cidr'))) | ||||
|  | ||||
|         if _id is not None: | ||||
|             return self.jsonify(SubnetManager().get_by_id(_id)) | ||||
|  | ||||
|         result, type2name = SubnetManager().tree_view() | ||||
|  | ||||
|         return self.jsonify(result=result, type2name=type2name) | ||||
|  | ||||
|     @args_required("cidr") | ||||
|     @args_required("parent_id", value_required=False) | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def post(self): | ||||
|         cidr = request.values.pop("cidr") | ||||
|         parent_id = request.values.pop("parent_id") | ||||
|         agent_id = request.values.pop("agent_id", None) | ||||
|         cron = request.values.pop("cron", None) | ||||
|  | ||||
|         return self.jsonify(SubnetManager().add(cidr, parent_id, agent_id, cron, **request.values)) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def put(self, _id): | ||||
|         return self.jsonify(id=SubnetManager().update(_id, **request.values)) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def delete(self, _id): | ||||
|         return self.jsonify(id=SubnetManager().delete(_id)) | ||||
|  | ||||
|  | ||||
| class SubnetScopeView(APIView): | ||||
|     url_prefix = ("/ipam/scope", "/ipam/scope/<int:_id>") | ||||
|  | ||||
|     @args_required("parent_id", value_required=False) | ||||
|     @args_required("name") | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def post(self): | ||||
|         parent_id = request.values.pop("parent_id") | ||||
|         name = request.values.pop("name") | ||||
|  | ||||
|         return self.jsonify(SubnetScopeManager().add(parent_id, name)) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def put(self, _id): | ||||
|         return self.jsonify(id=SubnetScopeManager().update(_id, **request.values)) | ||||
|  | ||||
|     @perms_role_required(app_cli.app_name, app_cli.resource_type_name, app_cli.op.IPAM, | ||||
|                          app_cli.op.read, app_cli.admin_name) | ||||
|     def delete(self, _id): | ||||
|         return self.jsonify(id=SubnetScopeManager.delete(_id)) | ||||
| @@ -16,7 +16,7 @@ Flask-Cors==4.0.0 | ||||
| Flask-Login>=0.6.2 | ||||
| Flask-Migrate==2.5.2 | ||||
| Flask-RESTful==0.3.10 | ||||
| Flask-SQLAlchemy==2.5.0 | ||||
| Flask-SQLAlchemy==3.0.5 | ||||
| future==0.18.3 | ||||
| gunicorn==21.0.1 | ||||
| hvac==2.0.0 | ||||
| @@ -57,3 +57,4 @@ lz4>=4.3.2 | ||||
| python-magic==0.4.27 | ||||
| jsonpath==0.82.2 | ||||
| networkx>=3.1 | ||||
| ipaddress>=1.0.23 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user