mirror of
https://github.com/veops/cmdb.git
synced 2025-08-08 18:33:46 +08:00
前后端全面升级
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import requests
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import session
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import CITypeOperateType
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import CITypeHistoryManager
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
@@ -18,27 +27,63 @@ class AttributeManager(object):
|
||||
"""
|
||||
CI attributes manager
|
||||
"""
|
||||
cls = Attribute
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_choice_values(attr_id, value_type):
|
||||
def _get_choice_values_from_web_hook(choice_web_hook):
|
||||
url = choice_web_hook.get('url')
|
||||
ret_key = choice_web_hook.get('ret_key')
|
||||
headers = choice_web_hook.get('headers') or {}
|
||||
payload = choice_web_hook.get('payload') or {}
|
||||
method = choice_web_hook.get('method', 'GET').lower()
|
||||
|
||||
try:
|
||||
res = getattr(requests, method)(url, headers=headers, data=payload).json()
|
||||
if ret_key:
|
||||
ret_key_list = ret_key.strip().split("##")
|
||||
for key in ret_key_list[:-1]:
|
||||
if key in res:
|
||||
res = res[key]
|
||||
if isinstance(res, list):
|
||||
return [[i[ret_key_list[-1]], {}] for i in res if i.get(ret_key_list[-1])]
|
||||
|
||||
return [[i, {}] for i in (res.get(ret_key_list[-1]) or [])]
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_choice_values(cls, attr_id, value_type, choice_web_hook, choice_web_hook_parse=True):
|
||||
if choice_web_hook and isinstance(choice_web_hook, dict) and choice_web_hook_parse:
|
||||
return cls._get_choice_values_from_web_hook(choice_web_hook)
|
||||
elif choice_web_hook and not choice_web_hook_parse:
|
||||
return []
|
||||
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
choice_values = choice_table.get_by(fl=["value"], attr_id=attr_id)
|
||||
return [choice_value["value"] for choice_value in choice_values]
|
||||
choice_values = choice_table.get_by(fl=["value", "option"], attr_id=attr_id)
|
||||
|
||||
return [[choice_value['value'], choice_value['option']] for choice_value in choice_values]
|
||||
|
||||
@staticmethod
|
||||
def _add_choice_values(_id, value_type, choice_values):
|
||||
def add_choice_values(_id, value_type, choice_values):
|
||||
choice_table = ValueTypeMap.choice.get(value_type)
|
||||
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
||||
db.session.flush()
|
||||
choice_values = choice_values
|
||||
for v in choice_values:
|
||||
table = choice_table(attr_id=_id, value=v)
|
||||
for v, option in choice_values:
|
||||
table = choice_table(attr_id=_id, value=v, option=option)
|
||||
|
||||
db.session.add(table)
|
||||
db.session.flush()
|
||||
|
||||
try:
|
||||
db.session.flush()
|
||||
except:
|
||||
return abort(400, ErrFormat.invalid_choice_values)
|
||||
|
||||
@staticmethod
|
||||
def _del_choice_values(_id, value_type):
|
||||
@@ -67,7 +112,10 @@ class AttributeManager(object):
|
||||
attrs = attrs[(page - 1) * page_size:][:page_size]
|
||||
res = list()
|
||||
for attr in attrs:
|
||||
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values(attr["id"], attr["value_type"])))
|
||||
attr["is_choice"] and attr.update(dict(choice_value=cls.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
attr['is_choice'] and attr.pop('choice_web_hook', None)
|
||||
|
||||
res.append(attr)
|
||||
|
||||
return numfound, res
|
||||
@@ -75,54 +123,72 @@ class AttributeManager(object):
|
||||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])))
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key):
|
||||
def get_attribute(self, key, choice_web_hook_parse=True):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
attr.update(dict(choice_value=self.get_choice_values(
|
||||
attr["id"], attr["value_type"], attr["choice_web_hook"])), choice_web_hook_parse=choice_web_hook_parse)
|
||||
return attr
|
||||
|
||||
@staticmethod
|
||||
def can_create_computed_attribute():
|
||||
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))
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
choice_value = kwargs.pop("choice_value", [])
|
||||
kwargs.pop("is_choice", None)
|
||||
is_choice = True if choice_value else False
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
|
||||
name = kwargs.pop("name")
|
||||
if name in {'id', '_id', 'ci_id', 'type', '_type', 'ci_type'}:
|
||||
return abort(400, ErrFormat.attribute_name_cannot_be_builtin)
|
||||
alias = kwargs.pop("alias", "")
|
||||
alias = name if not alias else alias
|
||||
Attribute.get_by(name=name, first=True) and abort(400, "attribute name <{0}> is duplicated".format(name))
|
||||
Attribute.get_by(alias=alias, first=True) and abort(400, "attribute alias <{0}> is duplicated".format(name))
|
||||
Attribute.get_by(name=name, first=True) and abort(400, ErrFormat.attribute_name_duplicate.format(name))
|
||||
|
||||
if kwargs.get('default') and not (isinstance(kwargs['default'], dict) and 'default' in kwargs['default']):
|
||||
kwargs['default'] = dict(default=kwargs['default'])
|
||||
|
||||
kwargs.get('is_computed') and cls.can_create_computed_attribute()
|
||||
|
||||
attr = Attribute.create(flush=True,
|
||||
name=name,
|
||||
alias=alias,
|
||||
is_choice=is_choice,
|
||||
uid=g.user.uid,
|
||||
**kwargs)
|
||||
|
||||
if choice_value:
|
||||
cls._add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
cls.add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add attribute error, {0}".format(str(e)))
|
||||
return abort(400, "add attribute <{0}> failed".format(name))
|
||||
|
||||
return abort(400, ErrFormat.add_attribute_failed.format(name))
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
@@ -144,27 +210,86 @@ class AttributeManager(object):
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
def _change_index(attr, old, new):
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.tasks.cmdb import batch_ci_cache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
|
||||
old_table = TableMap(attr=attr, is_index=old).table
|
||||
new_table = TableMap(attr=attr, is_index=new).table
|
||||
|
||||
ci_ids = []
|
||||
for i in db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id):
|
||||
new_table.create(ci_id=i.ci_id, attr_id=attr.id, value=i.value, flush=True)
|
||||
ci_ids.append(i.ci_id)
|
||||
|
||||
db.session.query(old_table).filter(getattr(old_table, 'attr_id') == attr.id).delete()
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(str(e))
|
||||
return abort(400, ErrFormat.attribute_index_change_failed)
|
||||
|
||||
batch_ci_cache.apply_async(args=(ci_ids,), queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def _can_edit_attribute(attr):
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
|
||||
if attr.uid == g.user.uid:
|
||||
return True
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=attr.id, to_dict=False):
|
||||
resource = CITypeManager.get_name_by_id(i.type_id)
|
||||
if resource:
|
||||
validate_permission(resource, ResourceTypeEnum.CI, PermEnum.CONFIG, "cmdb")
|
||||
|
||||
return True
|
||||
|
||||
def update(self, _id, **kwargs):
|
||||
attr = Attribute.get_by_id(_id) or abort(404, "Attribute <{0}> does not exist".format(_id))
|
||||
attr = Attribute.get_by_id(_id) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_id)))
|
||||
|
||||
if not self._can_edit_attribute(attr):
|
||||
return abort(403, ErrFormat.cannot_edit_attribute)
|
||||
|
||||
if kwargs.get("name"):
|
||||
other = Attribute.get_by(name=kwargs['name'], first=True, to_dict=False)
|
||||
if other and other.id != attr.id:
|
||||
return abort(400, "Attribute name <{0}> cannot be duplicate!".format(kwargs['name']))
|
||||
if kwargs.get("alias"):
|
||||
other = Attribute.get_by(alias=kwargs['alias'], first=True, to_dict=False)
|
||||
if other and other.id != attr.id:
|
||||
return abort(400, "Attribute alias <{0}> cannot be duplicate!".format(kwargs['alias']))
|
||||
return abort(400, ErrFormat.attribute_name_duplicate.format(kwargs['name']))
|
||||
|
||||
if attr.value_type != kwargs.get('value_type'):
|
||||
return abort(400, ErrFormat.attribute_value_type_cannot_change)
|
||||
|
||||
if "is_list" in kwargs and kwargs['is_list'] != attr.is_list:
|
||||
return abort(400, ErrFormat.attribute_list_value_cannot_change)
|
||||
|
||||
if "is_index" in kwargs and kwargs['is_index'] != attr.is_index:
|
||||
if not is_app_admin("cmdb"):
|
||||
return abort(400, ErrFormat.attribute_index_cannot_change)
|
||||
|
||||
self._change_index(attr, attr.is_index, kwargs['is_index'])
|
||||
|
||||
existed2 = attr.to_dict()
|
||||
if not existed2['choice_web_hook'] and existed2['is_choice']:
|
||||
existed2['choice_value'] = self.get_choice_values(attr.id, attr.value_type, attr.choice_web_hook)
|
||||
|
||||
choice_value = kwargs.pop("choice_value", False)
|
||||
is_choice = True if choice_value else False
|
||||
is_choice = True if choice_value or kwargs.get('choice_web_hook') else False
|
||||
kwargs['is_choice'] = is_choice
|
||||
|
||||
attr.update(flush=True, **kwargs)
|
||||
if kwargs.get('default') and not (isinstance(kwargs['default'], dict) and 'default' in kwargs['default']):
|
||||
kwargs['default'] = dict(default=kwargs['default'])
|
||||
|
||||
if is_choice:
|
||||
self._add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
else:
|
||||
kwargs.get('is_computed') and self.can_create_computed_attribute()
|
||||
|
||||
attr.update(flush=True, filter_none=False, **kwargs)
|
||||
|
||||
if is_choice and choice_value:
|
||||
self.add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
elif is_choice:
|
||||
self._del_choice_values(attr.id, attr.value_type)
|
||||
|
||||
try:
|
||||
@@ -172,7 +297,14 @@ class AttributeManager(object):
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("update attribute error, {0}".format(str(e)))
|
||||
return abort(400, "update attribute <{0}> failed".format(_id))
|
||||
|
||||
return abort(400, ErrFormat.update_attribute_failed.format(("id=".format(_id))))
|
||||
|
||||
new = attr.to_dict()
|
||||
if not new['choice_web_hook'] and new['is_choice']:
|
||||
new['choice_value'] = choice_value
|
||||
CITypeHistoryManager.add(CITypeOperateType.UPDATE_ATTRIBUTE, None, attr_id=attr.id,
|
||||
change=dict(old=existed2, new=new))
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
@@ -180,9 +312,12 @@ class AttributeManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
attr = Attribute.get_by_id(_id) or abort(404, "Attribute <{0}> does not exist".format(_id))
|
||||
attr = Attribute.get_by_id(_id) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_id)))
|
||||
name = attr.name
|
||||
|
||||
if attr.uid and attr.uid != g.user.uid:
|
||||
return abort(403, ErrFormat.cannot_delete_attribute)
|
||||
|
||||
if attr.is_choice:
|
||||
choice_table = ValueTypeMap.choice.get(attr.value_type)
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict
|
||||
|
1
cmdb-api/api/lib/cmdb/auto_discovery/__init__.py
Normal file
1
cmdb-api/api/lib/cmdb/auto_discovery/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding:utf-8 -*-
|
505
cmdb-api/api/lib/cmdb/auto_discovery/auto_discovery.py
Normal file
505
cmdb-api/api/lib/cmdb/auto_discovery/auto_discovery.py
Normal file
@@ -0,0 +1,505 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from sqlalchemy import func
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
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.ci_type import CITypeGroupManager
|
||||
from api.lib.cmdb.const import AutoDiscoveryType
|
||||
from api.lib.cmdb.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
from api.lib.mixin import DBMixin
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import AESCrypto
|
||||
from api.models.cmdb import AutoDiscoveryCI
|
||||
from api.models.cmdb import AutoDiscoveryCIType
|
||||
from api.models.cmdb import AutoDiscoveryRule
|
||||
|
||||
PWD = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def parse_plugin_script(script):
|
||||
attributes = []
|
||||
try:
|
||||
x = compile(script, '', "exec")
|
||||
exec(x)
|
||||
unique_key = locals()['AutoDiscovery']().unique_key
|
||||
attrs = locals()['AutoDiscovery']().attributes() or []
|
||||
except Exception as e:
|
||||
return abort(400, str(e))
|
||||
|
||||
if not isinstance(attrs, list):
|
||||
return abort(400, ErrFormat.adr_plugin_attributes_list_required)
|
||||
|
||||
for i in attrs:
|
||||
if len(i) == 3:
|
||||
name, _type, desc = i
|
||||
elif len(i) == 2:
|
||||
name, _type = i
|
||||
desc = ""
|
||||
else:
|
||||
continue
|
||||
attributes.append(dict(name=name, type=_type, desc=desc))
|
||||
|
||||
return unique_key, attributes
|
||||
|
||||
|
||||
def check_plugin_script(**kwargs):
|
||||
kwargs['unique_key'], kwargs['attributes'] = parse_plugin_script(kwargs['plugin_script'])
|
||||
|
||||
if not kwargs.get('unique_key'):
|
||||
return abort(400, ErrFormat.adr_unique_key_required)
|
||||
|
||||
if not kwargs.get('attributes'):
|
||||
return abort(400, ErrFormat.adr_plugin_attributes_list_no_empty)
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class AutoDiscoveryRuleCRUD(DBMixin):
|
||||
cls = AutoDiscoveryRule
|
||||
|
||||
@classmethod
|
||||
def get_by_name(cls, name):
|
||||
return cls.cls.get_by(name=name, first=True, to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, _id):
|
||||
return cls.cls.get_by_id(_id)
|
||||
|
||||
def get_by_inner(self):
|
||||
return self.cls.get_by(is_inner=True, to_dict=True)
|
||||
|
||||
def import_template(self, rules):
|
||||
for rule in rules:
|
||||
rule.pop("id", None)
|
||||
rule.pop("created_at", None)
|
||||
rule.pop("updated_at", None)
|
||||
|
||||
existed = self.cls.get_by(name=rule['name'], first=True, to_dict=False)
|
||||
if existed is not None:
|
||||
existed.update(**rule)
|
||||
else:
|
||||
self.cls.create(**rule)
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
self.cls.get_by(name=kwargs['name']) and abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
||||
return kwargs
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
existed = self.cls.get_by_id(kwargs['_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['_id'])))
|
||||
|
||||
if 'name' in kwargs and not kwargs['name']:
|
||||
return abort(400, ErrFormat.argument_value_required.format('name'))
|
||||
|
||||
if kwargs.get('name'):
|
||||
other = self.cls.get_by(name=kwargs['name'], first=True, to_dict=False)
|
||||
if other and other.id != existed.id:
|
||||
return abort(400, ErrFormat.adr_duplicate.format(kwargs['name']))
|
||||
|
||||
return existed
|
||||
|
||||
def update(self, _id, **kwargs):
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
||||
return super(AutoDiscoveryRuleCRUD, self).update(_id, filter_none=False, **kwargs)
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
if AutoDiscoveryCIType.get_by(adr_id=kwargs['_id'], first=True):
|
||||
return abort(400, ErrFormat.adr_referenced)
|
||||
|
||||
return self._can_update(**kwargs)
|
||||
|
||||
|
||||
class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||
cls = AutoDiscoveryCIType
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return cls.cls.get_by(to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, _id):
|
||||
return cls.cls.get_by_id(_id)
|
||||
|
||||
@classmethod
|
||||
def get_by_type_id(cls, type_id):
|
||||
return cls.cls.get_by(type_id=type_id, to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get(cls, ci_id, oneagent_id, last_update_at=None):
|
||||
result = []
|
||||
rules = cls.cls.get_by(to_dict=True)
|
||||
|
||||
for rule in rules:
|
||||
if rule.get('relation'):
|
||||
continue
|
||||
|
||||
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'):
|
||||
if not (g.user.username == "cmdb_agent" or g.user.uid == rule['uid']):
|
||||
rule['extra_option'].pop('secret', None)
|
||||
else:
|
||||
rule['extra_option']['secret'] = AESCrypto.decrypt(rule['extra_option']['secret'])
|
||||
|
||||
if oneagent_id and rule['agent_id'] == oneagent_id:
|
||||
result.append(rule)
|
||||
elif rule['query_expr']:
|
||||
query = rule['query_expr'].lstrip('q').lstrip('=')
|
||||
s = search(query, fl=['_id'], count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
|
||||
for i in (response or []):
|
||||
if i.get('_id') == ci_id:
|
||||
result.append(rule)
|
||||
break
|
||||
elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']:
|
||||
adr = AutoDiscoveryRuleCRUD.get_by_id(rule['adr_id'])
|
||||
if not adr:
|
||||
continue
|
||||
if adr.type in (AutoDiscoveryType.SNMP, AutoDiscoveryType.HTTP):
|
||||
continue
|
||||
|
||||
if not rule['updated_at']:
|
||||
continue
|
||||
|
||||
result.append(rule)
|
||||
|
||||
new_last_update_at = ""
|
||||
for i in result:
|
||||
i['adr'] = AutoDiscoveryRule.get_by_id(i['adr_id']).to_dict()
|
||||
__last_update_at = max([i['updated_at'] or "", i['created_at'] or "",
|
||||
i['adr']['created_at'] or "", i['adr']['updated_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 __valid_exec_target(agent_id, query_expr):
|
||||
_is_app_admin = is_app_admin("cmdb")
|
||||
if not agent_id and not query_expr and not _is_app_admin:
|
||||
return abort(403, ErrFormat.adt_target_all_no_permission)
|
||||
|
||||
if _is_app_admin:
|
||||
return
|
||||
|
||||
if agent_id and isinstance(agent_id, str) and agent_id.startswith("0x"):
|
||||
agent_id = agent_id.strip()
|
||||
q = "op_duty:{0},-rd_duty:{0},oneagent_id:{1}"
|
||||
|
||||
s = search(q.format(g.user.username, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
return
|
||||
except SearchError as e:
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
s = search(q.format(g.user.nickname, agent_id.strip()))
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
if response:
|
||||
return
|
||||
except SearchError as e:
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
if query_expr.strip():
|
||||
query_expr = query_expr.strip()
|
||||
if query_expr.startswith('q='):
|
||||
query_expr = query_expr[2:]
|
||||
|
||||
s = search(query_expr, count=1000000)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
for i in response:
|
||||
if g.user.username not in (i.get('rd_duty') or []) and g.user.username not in \
|
||||
(i.get('op_duty') or []) and g.user.nickname not in (i.get('rd_duty') or []) and \
|
||||
g.user.nickname not in (i.get('op_duty') or []):
|
||||
return abort(403, ErrFormat.adt_target_expr_no_permission.format(
|
||||
i.get("{}_name".format(i.get('ci_type')))))
|
||||
except SearchError as e:
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
self.cls.get_by(type_id=kwargs['type_id'], adr_id=kwargs.get('adr_id') or None) and abort(
|
||||
400, ErrFormat.ad_duplicate)
|
||||
|
||||
# self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
|
||||
if kwargs.get('adr_id'):
|
||||
adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
|
||||
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
|
||||
if not adr.is_plugin:
|
||||
other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
|
||||
if other:
|
||||
ci_type = CITypeCache.get(other.type_id)
|
||||
return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
|
||||
|
||||
kwargs['uid'] = g.user.uid
|
||||
|
||||
return kwargs
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
existed = self.cls.get_by_id(kwargs['_id']) or abort(
|
||||
404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id'])))
|
||||
|
||||
self.__valid_exec_target(kwargs.get('agent_id'), kwargs.get('query_expr'))
|
||||
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
if g.user.uid != existed.uid:
|
||||
return abort(403, ErrFormat.adt_secret_no_permission)
|
||||
|
||||
return existed
|
||||
|
||||
def update(self, _id, **kwargs):
|
||||
|
||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||
kwargs = check_plugin_script(**kwargs)
|
||||
|
||||
if isinstance(kwargs.get('extra_option'), dict) and kwargs['extra_option'].get('secret'):
|
||||
kwargs['extra_option']['secret'] = AESCrypto.encrypt(kwargs['extra_option']['secret'])
|
||||
|
||||
return super(AutoDiscoveryCITypeCRUD, self).update(_id, filter_none=False, **kwargs)
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
if AutoDiscoveryCICRUD.get_by_adt_id(kwargs['_id']):
|
||||
return abort(400, ErrFormat.cannot_delete_adt)
|
||||
|
||||
existed = self.cls.get_by_id(kwargs['_id']) or abort(
|
||||
404, ErrFormat.ad_not_found.format("id={}".format(kwargs['_id'])))
|
||||
|
||||
return existed
|
||||
|
||||
|
||||
class AutoDiscoveryCICRUD(DBMixin):
|
||||
cls = AutoDiscoveryCI
|
||||
|
||||
@classmethod
|
||||
def get_by_adt_id(cls, adt_id):
|
||||
return cls.cls.get_by(adt_id=adt_id, to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get_type_name(cls, adc_id):
|
||||
adc = cls.cls.get_by_id(adc_id) or abort(404, ErrFormat.adc_not_found)
|
||||
|
||||
ci_type = CITypeCache.get(adc.type_id)
|
||||
|
||||
return ci_type and ci_type.name
|
||||
|
||||
@staticmethod
|
||||
def get_ci_types(need_other):
|
||||
result = CITypeGroupManager.get(need_other, False)
|
||||
adt = {i.type_id for i in AutoDiscoveryCITypeCRUD.get_all()}
|
||||
for item in result:
|
||||
item['ci_types'] = [i for i in (item.get('ci_types') or []) if i['id'] in adt]
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
attributes = [i[1] for i in CITypeAttributesCache.get2(type_id) or []]
|
||||
|
||||
attr_names = set()
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
for adt in adts:
|
||||
attr_names |= set((adt.attributes or {}).values())
|
||||
|
||||
return [attr.to_dict() for attr in attributes if attr.name in attr_names]
|
||||
|
||||
@classmethod
|
||||
def search(cls, page, page_size, fl=None, **kwargs):
|
||||
type_id = kwargs['type_id']
|
||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||
if not adts:
|
||||
return 0, []
|
||||
adt2attr_map = {i.id: i.attributes or {} for i in adts}
|
||||
|
||||
query = db.session.query(cls.cls).filter(cls.cls.deleted.is_(False))
|
||||
|
||||
count_query = db.session.query(func.count(cls.cls.id)).filter(cls.cls.deleted.is_(False))
|
||||
|
||||
for k in kwargs:
|
||||
if hasattr(cls.cls, k):
|
||||
query = query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
count_query = count_query.filter(getattr(cls.cls, k) == kwargs[k])
|
||||
|
||||
query = query.order_by(cls.cls.is_accept.desc()).order_by(cls.cls.id.desc())
|
||||
|
||||
result = []
|
||||
for i in query.offset((page - 1) * page_size).limit(page_size):
|
||||
item = i.to_dict()
|
||||
adt_id = item['adt_id']
|
||||
item['instance'] = {adt2attr_map[adt_id][k]: v for k, v in item.get('instance').items()
|
||||
if (not fl or k in fl) and adt2attr_map.get(adt_id, {}).get(k)}
|
||||
result.append(item)
|
||||
|
||||
numfound = query.count()
|
||||
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def _get_unique_key(type_id):
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type:
|
||||
attr = CITypeAttributeCache.get(type_id, ci_type.unique_id)
|
||||
return attr and attr.name
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
pass
|
||||
|
||||
def upsert(self, **kwargs):
|
||||
|
||||
adt = AutoDiscoveryCIType.get_by_id(kwargs['adt_id']) or abort(404, ErrFormat.adt_not_found)
|
||||
|
||||
existed = self.cls.get_by(type_id=kwargs['type_id'],
|
||||
unique_value=kwargs.get("unique_value"),
|
||||
first=True, to_dict=False)
|
||||
changed = False
|
||||
if existed is not None:
|
||||
if existed.instance != kwargs['instance']:
|
||||
existed.update(filter_none=False, **kwargs)
|
||||
changed = True
|
||||
else:
|
||||
existed = self.cls.create(**kwargs)
|
||||
changed = True
|
||||
|
||||
if adt.auto_accept and changed:
|
||||
try:
|
||||
self.accept(existed)
|
||||
except Exception as e:
|
||||
return abort(400, str(e))
|
||||
elif changed:
|
||||
existed.update(is_accept=False, accept_time=None, accept_by=None, filter_none=False)
|
||||
|
||||
return existed
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
existed = self.cls.get_by_id(kwargs['_id']) or abort(404, ErrFormat.adc_not_found)
|
||||
|
||||
return existed
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
return self._can_update(**kwargs)
|
||||
|
||||
def delete(self, _id):
|
||||
inst = self._can_delete(_id=_id)
|
||||
|
||||
inst.delete()
|
||||
|
||||
self._after_delete(inst)
|
||||
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def delete2(cls, type_id, unique_value):
|
||||
existed = cls.cls.get_by(type_id=type_id, unique_value=unique_value, first=True, to_dict=False) or abort(
|
||||
404, ErrFormat.adc_not_found)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ci_type = CITypeCache.get(type_id) or abort(404, ErrFormat.ci_type_not_found)
|
||||
|
||||
not is_app_admin("cmdb") and validate_permission(ci_type.name, ResourceTypeEnum.CI, PermEnum.DELETE, "cmdb")
|
||||
|
||||
existed.delete()
|
||||
# TODO: delete ci
|
||||
|
||||
@classmethod
|
||||
def accept(cls, adc, adc_id=None, nickname=None):
|
||||
if adc_id is not None:
|
||||
adc = cls.cls.get_by_id(adc_id) or abort(404, ErrFormat.adc_not_found)
|
||||
|
||||
adt = AutoDiscoveryCITypeCRUD.get_by_id(adc.adt_id) or abort(404, ErrFormat.adt_not_found)
|
||||
|
||||
ci_id = None
|
||||
if adt.attributes:
|
||||
ci_dict = {adt.attributes[k]: v for k, v in adc.instance.items() if k in adt.attributes}
|
||||
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, **ci_dict)
|
||||
|
||||
relation_adts = AutoDiscoveryCIType.get_by(type_id=adt.type_id, adr_id=None, to_dict=False)
|
||||
for r_adt in relation_adts:
|
||||
if r_adt.relation and ci_id is not None:
|
||||
ad_key, cmdb_key = None, {}
|
||||
for ad_key in r_adt.relation:
|
||||
cmdb_key = r_adt.relation[ad_key]
|
||||
query = "_type:{},{}:{}".format(cmdb_key.get('type_name'), cmdb_key.get('attr_name'),
|
||||
adc.instance.get(ad_key))
|
||||
s = search(query)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
relation_ci_id = response and response[0]['_id']
|
||||
if relation_ci_id:
|
||||
try:
|
||||
CIRelationManager.add(ci_id, relation_ci_id)
|
||||
except:
|
||||
try:
|
||||
CIRelationManager.add(relation_ci_id, ci_id)
|
||||
except:
|
||||
pass
|
||||
|
||||
adc.update(is_accept=True, accept_by=nickname or g.user.nickname, accept_time=datetime.datetime.now())
|
||||
|
||||
|
||||
class AutoDiscoveryHTTPManager(object):
|
||||
@staticmethod
|
||||
def get_categories(name):
|
||||
return (ClOUD_MAP.get(name) or {}).get('categories') or []
|
||||
|
||||
@staticmethod
|
||||
def get_attributes(name, category):
|
||||
tpt = ((ClOUD_MAP.get(name) or {}).get('map') or {}).get(category)
|
||||
if tpt and os.path.exists(os.path.join(PWD, tpt)):
|
||||
with open(os.path.join(PWD, tpt)) as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class AutoDiscoverySNMPManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_attributes():
|
||||
if os.path.exists(os.path.join(PWD, "templates/net_device.json")):
|
||||
with open(os.path.join(PWD, "templates/net_device.json")) as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
return []
|
53
cmdb-api/api/lib/cmdb/auto_discovery/const.py
Normal file
53
cmdb-api/api/lib/cmdb/auto_discovery/const.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.cmdb.const import AutoDiscoveryType
|
||||
|
||||
DEFAULT_HTTP = [
|
||||
dict(name="阿里云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-aliyun'}}),
|
||||
dict(name="腾讯云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-tengxunyun'}}),
|
||||
dict(name="华为云", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-huaweiyun'}}),
|
||||
dict(name="AWS", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-aws'}}),
|
||||
|
||||
dict(name="交换机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-jiaohuanji'}}),
|
||||
dict(name="路由器", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-luyouqi'}}),
|
||||
dict(name="防火墙", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-fanghuoqiang'}}),
|
||||
dict(name="打印机", type=AutoDiscoveryType.SNMP, is_inner=True, is_plugin=False,
|
||||
option={'icon': {'name': 'caise-dayinji'}}),
|
||||
]
|
||||
|
||||
ClOUD_MAP = {
|
||||
"aliyun": {
|
||||
"categories": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": "templates/aliyun_ecs.json",
|
||||
}
|
||||
},
|
||||
|
||||
"tencentcloud": {
|
||||
"categories": ["云服务器 CVM"],
|
||||
"map": {
|
||||
"云服务器 CVM": "templates/tencent_cvm.json",
|
||||
}
|
||||
},
|
||||
|
||||
"huaweicloud": {
|
||||
"categories": ["云服务器 ECS"],
|
||||
"map": {
|
||||
"云服务器 ECS": "templates/huaweicloud_ecs.json",
|
||||
}
|
||||
},
|
||||
|
||||
"aws": {
|
||||
"categories": ["云服务器 EC2"],
|
||||
"map": {
|
||||
"云服务器 EC2": "templates/aws_ec2.json",
|
||||
}
|
||||
},
|
||||
}
|
647
cmdb-api/api/lib/cmdb/auto_discovery/templates/aliyun_ecs.json
Normal file
647
cmdb-api/api/lib/cmdb/auto_discovery/templates/aliyun_ecs.json
Normal file
@@ -0,0 +1,647 @@
|
||||
[
|
||||
{
|
||||
"name": "CreationTime",
|
||||
"type": "文本",
|
||||
"example": "2017-12-10T04:04Z",
|
||||
"desc": "\u5b9e\u4f8b\u521b\u5efa\u65f6\u95f4\u3002\u4ee5ISO 8601\u4e3a\u6807\u51c6\uff0c\u5e76\u4f7f\u7528UTC+0\u65f6\u95f4\uff0c\u683c\u5f0f\u4e3ayyyy-MM-ddTHH:mmZ\u3002\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u89c1[ISO 8601](~~25696~~)\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SerialNumber",
|
||||
"type": "文本",
|
||||
"example": "51d1353b-22bf-4567-a176-8b3e12e4****",
|
||||
"desc": "\u5b9e\u4f8b\u5e8f\u5217\u53f7\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Status",
|
||||
"type": "文本",
|
||||
"example": "Running",
|
||||
"desc": "\u5b9e\u4f8b\u72b6\u6001\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DeploymentSetId",
|
||||
"type": "文本",
|
||||
"example": "ds-bp67acfmxazb4p****",
|
||||
"desc": "\u90e8\u7f72\u96c6ID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "KeyPairName",
|
||||
"type": "文本",
|
||||
"example": "testKeyPairName",
|
||||
"desc": "\u5bc6\u94a5\u5bf9\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SaleCycle",
|
||||
"type": "文本",
|
||||
"example": "month",
|
||||
"desc": "> \u8be5\u53c2\u6570\u5df2\u5f03\u7528\uff0c\u4e0d\u518d\u8fd4\u56de\u6709\u610f\u4e49\u7684\u6570\u636e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SpotStrategy",
|
||||
"type": "文本",
|
||||
"example": "NoSpot",
|
||||
"desc": "\u6309\u91cf\u5b9e\u4f8b\u7684\u7ade\u4ef7\u7b56\u7565\u3002\u53ef\u80fd\u503c\uff1a\n\n- NoSpot\uff1a\u6b63\u5e38\u6309\u91cf\u4ed8\u8d39\u5b9e\u4f8b\u3002\n- SpotWithPriceLimit\uff1a\u8bbe\u7f6e\u4e0a\u9650\u4ef7\u683c\u7684\u62a2\u5360\u5f0f\u5b9e\u4f8b\u3002\n- SpotAsPriceGo\uff1a\u7cfb\u7edf\u81ea\u52a8\u51fa\u4ef7\uff0c\u6700\u9ad8\u6309\u91cf\u4ed8\u8d39\u4ef7\u683c\u7684\u62a2\u5360\u5f0f\u5b9e\u4f8b\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DeviceAvailable",
|
||||
"type": "boolean",
|
||||
"example": "true",
|
||||
"desc": "\u5b9e\u4f8b\u662f\u5426\u53ef\u4ee5\u6302\u8f7d\u6570\u636e\u76d8\u3002"
|
||||
},
|
||||
{
|
||||
"name": "LocalStorageCapacity",
|
||||
"type": "整数",
|
||||
"example": "1000",
|
||||
"desc": "\u5b9e\u4f8b\u6302\u8f7d\u7684\u672c\u5730\u5b58\u50a8\u5bb9\u91cf\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Description",
|
||||
"type": "文本",
|
||||
"example": "testDescription",
|
||||
"desc": "\u5b9e\u4f8b\u63cf\u8ff0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SpotDuration",
|
||||
"type": "整数",
|
||||
"example": "1",
|
||||
"desc": "\u62a2\u5360\u5f0f\u5b9e\u4f8b\u7684\u4fdd\u7559\u65f6\u957f\uff0c\u5355\u4f4d\u4e3a\u5c0f\u65f6\u3002\u53ef\u80fd\u503c\u4e3a0~6\u3002\n\n- \u4fdd\u7559\u65f6\u957f2~6\u6b63\u5728\u9080\u6d4b\u4e2d\uff0c\u5982\u9700\u5f00\u901a\u8bf7\u63d0\u4ea4\u5de5\u5355\u3002\n- \u503c\u4e3a0\uff0c\u5219\u4e3a\u65e0\u4fdd\u62a4\u671f\u6a21\u5f0f\u3002\n\n>\u5f53SpotStrategy\u503c\u4e3aSpotWithPriceLimit\u6216SpotAsPriceGo\u65f6\u8fd4\u56de\u8be5\u53c2\u6570\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceNetworkType",
|
||||
"type": "文本",
|
||||
"example": "vpc",
|
||||
"desc": "\u5b9e\u4f8b\u7f51\u7edc\u7c7b\u578b\u3002\u53ef\u80fd\u503c\uff1a\n\n- classic\uff1a\u7ecf\u5178\u7f51\u7edc\u3002\n- vpc\uff1a\u4e13\u6709\u7f51\u7edcVPC\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceName",
|
||||
"type": "文本",
|
||||
"example": "InstanceNameTest",
|
||||
"desc": "\u5b9e\u4f8b\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OSNameEn",
|
||||
"type": "文本",
|
||||
"example": "CentOS 7.4 64 bit",
|
||||
"desc": "\u5b9e\u4f8b\u64cd\u4f5c\u7cfb\u7edf\u7684\u82f1\u6587\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "HpcClusterId",
|
||||
"type": "文本",
|
||||
"example": "hpc-bp67acfmxazb4p****",
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5c5e\u7684HPC\u96c6\u7fa4ID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SpotPriceLimit",
|
||||
"type": "float",
|
||||
"example": "0.98",
|
||||
"desc": "\u5b9e\u4f8b\u7684\u6bcf\u5c0f\u65f6\u6700\u9ad8\u4ef7\u683c\u3002\u652f\u6301\u6700\u59273\u4f4d\u5c0f\u6570\uff0c\u53c2\u6570SpotStrategy=SpotWithPriceLimit\u65f6\uff0c\u8be5\u53c2\u6570\u751f\u6548\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"type": "整数",
|
||||
"example": "16384",
|
||||
"desc": "\u5185\u5b58\u5927\u5c0f\uff0c\u5355\u4f4d\u4e3aMiB\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OSName",
|
||||
"type": "文本",
|
||||
"example": "CentOS 7.4 64 \u4f4d",
|
||||
"desc": "\u5b9e\u4f8b\u7684\u64cd\u4f5c\u7cfb\u7edf\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DeploymentSetGroupNo",
|
||||
"type": "整数",
|
||||
"example": "1",
|
||||
"desc": "ECS\u5b9e\u4f8b\u7ed1\u5b9a\u90e8\u7f72\u96c6\u5206\u6563\u90e8\u7f72\u65f6\uff0c\u5b9e\u4f8b\u5728\u90e8\u7f72\u96c6\u4e2d\u7684\u5206\u7ec4\u4f4d\u7f6e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ImageId",
|
||||
"type": "文本",
|
||||
"example": "m-bp67acfmxazb4p****",
|
||||
"desc": "\u5b9e\u4f8b\u8fd0\u884c\u7684\u955c\u50cfID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "VlanId",
|
||||
"type": "文本",
|
||||
"example": "10",
|
||||
"desc": "\u5b9e\u4f8b\u7684VLAN ID\u3002\n\n>\u8be5\u53c2\u6570\u5373\u5c06\u88ab\u5f03\u7528\uff0c\u4e3a\u63d0\u9ad8\u517c\u5bb9\u6027\uff0c\u8bf7\u5c3d\u91cf\u4f7f\u7528\u5176\u4ed6\u53c2\u6570\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ClusterId",
|
||||
"type": "文本",
|
||||
"example": "c-bp67acfmxazb4p****",
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5728\u7684\u96c6\u7fa4ID\u3002\n\n>\u8be5\u53c2\u6570\u5373\u5c06\u88ab\u5f03\u7528\uff0c\u4e3a\u63d0\u9ad8\u517c\u5bb9\u6027\uff0c\u8bf7\u5c3d\u91cf\u4f7f\u7528\u5176\u4ed6\u53c2\u6570\u3002"
|
||||
},
|
||||
{
|
||||
"name": "GPUSpec",
|
||||
"type": "文本",
|
||||
"example": "NVIDIA V100",
|
||||
"desc": "\u5b9e\u4f8b\u89c4\u683c\u9644\u5e26\u7684GPU\u7c7b\u578b\u3002"
|
||||
},
|
||||
{
|
||||
"name": "AutoReleaseTime",
|
||||
"type": "文本",
|
||||
"example": "2017-12-10T04:04Z",
|
||||
"desc": "\u6309\u91cf\u4ed8\u8d39\u5b9e\u4f8b\u7684\u81ea\u52a8\u91ca\u653e\u65f6\u95f4\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DeletionProtection",
|
||||
"type": "boolean",
|
||||
"example": "false",
|
||||
"desc": "\u5b9e\u4f8b\u91ca\u653e\u4fdd\u62a4\u5c5e\u6027\uff0c\u6307\u5b9a\u662f\u5426\u652f\u6301\u901a\u8fc7\u63a7\u5236\u53f0\u6216API\uff08DeleteInstance\uff09\u91ca\u653e\u5b9e\u4f8b\u3002\n\n- true\uff1a\u5df2\u5f00\u542f\u5b9e\u4f8b\u91ca\u653e\u4fdd\u62a4\u3002\n- false\uff1a\u672a\u5f00\u542f\u5b9e\u4f8b\u91ca\u653e\u4fdd\u62a4\u3002\n\n> \u8be5\u5c5e\u6027\u4ec5\u9002\u7528\u4e8e\u6309\u91cf\u4ed8\u8d39\u5b9e\u4f8b\uff0c\u4e14\u53ea\u80fd\u9650\u5236\u624b\u52a8\u91ca\u653e\u64cd\u4f5c\uff0c\u5bf9\u7cfb\u7edf\u91ca\u653e\u64cd\u4f5c\u4e0d\u751f\u6548\u3002"
|
||||
},
|
||||
{
|
||||
"name": "StoppedMode",
|
||||
"type": "文本",
|
||||
"example": "KeepCharging",
|
||||
"desc": "\u5b9e\u4f8b\u505c\u673a\u540e\u662f\u5426\u7ee7\u7eed\u6536\u8d39\u3002\u53ef\u80fd\u503c\uff1a\n\n- KeepCharging\uff1a\u505c\u673a\u540e\u7ee7\u7eed\u6536\u8d39\uff0c\u4e3a\u60a8\u7ee7\u7eed\u4fdd\u7559\u5e93\u5b58\u8d44\u6e90\u3002\n- StopCharging\uff1a\u505c\u673a\u540e\u4e0d\u6536\u8d39\u3002\u505c\u673a\u540e\uff0c\u6211\u4eec\u91ca\u653e\u5b9e\u4f8b\u5bf9\u5e94\u7684\u8d44\u6e90\uff0c\u4f8b\u5982vCPU\u3001\u5185\u5b58\u548c\u516c\u7f51IP\u7b49\u8d44\u6e90\u3002\u91cd\u542f\u662f\u5426\u6210\u529f\u4f9d\u8d56\u4e8e\u5f53\u524d\u5730\u57df\u4e2d\u662f\u5426\u4ecd\u6709\u8d44\u6e90\u5e93\u5b58\u3002\n- Not-applicable\uff1a\u672c\u5b9e\u4f8b\u4e0d\u652f\u6301\u505c\u673a\u4e0d\u6536\u8d39\u529f\u80fd\u3002"
|
||||
},
|
||||
{
|
||||
"name": "GPUAmount",
|
||||
"type": "整数",
|
||||
"example": "4",
|
||||
"desc": "\u5b9e\u4f8b\u89c4\u683c\u9644\u5e26\u7684GPU\u6570\u91cf\u3002"
|
||||
},
|
||||
{
|
||||
"name": "HostName",
|
||||
"type": "文本",
|
||||
"example": "testHostName",
|
||||
"desc": "\u5b9e\u4f8b\u4e3b\u673a\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceId",
|
||||
"type": "文本",
|
||||
"example": "i-bp67acfmxazb4p****",
|
||||
"desc": "\u5b9e\u4f8bID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InternetMaxBandwidthOut",
|
||||
"type": "整数",
|
||||
"example": "5",
|
||||
"desc": "\u516c\u7f51\u51fa\u5e26\u5bbd\u6700\u5927\u503c\uff0c\u5355\u4f4d\u4e3aMbit/s\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InternetMaxBandwidthIn",
|
||||
"type": "整数",
|
||||
"example": "50",
|
||||
"desc": "\u516c\u7f51\u5165\u5e26\u5bbd\u6700\u5927\u503c\uff0c\u5355\u4f4d\u4e3aMbit/s\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceType",
|
||||
"type": "文本",
|
||||
"example": "ecs.g5.large",
|
||||
"desc": "\u5b9e\u4f8b\u89c4\u683c\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceChargeType",
|
||||
"type": "文本",
|
||||
"example": "PostPaid",
|
||||
"desc": "\u5b9e\u4f8b\u7684\u8ba1\u8d39\u65b9\u5f0f\u3002\u53ef\u80fd\u503c\uff1a\n\n- PrePaid\uff1a\u5305\u5e74\u5305\u6708\u3002\n- PostPaid\uff1a\u6309\u91cf\u4ed8\u8d39\u3002"
|
||||
},
|
||||
{
|
||||
"name": "RegionId",
|
||||
"type": "文本",
|
||||
"example": "cn-hangzhou",
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5c5e\u5730\u57dfID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "IoOptimized",
|
||||
"type": "boolean",
|
||||
"example": "true",
|
||||
"desc": "\u662f\u5426\u4e3aI/O\u4f18\u5316\u578b\u5b9e\u4f8b\u3002"
|
||||
},
|
||||
{
|
||||
"name": "StartTime",
|
||||
"type": "文本",
|
||||
"example": "2017-12-10T04:04Z",
|
||||
"desc": "\u5b9e\u4f8b\u6700\u8fd1\u4e00\u6b21\u7684\u542f\u52a8\u65f6\u95f4\u3002\u4ee5ISO8601\u4e3a\u6807\u51c6\uff0c\u5e76\u4f7f\u7528UTC+0\u65f6\u95f4\uff0c\u683c\u5f0f\u4e3ayyyy-MM-ddTHH:mmZ\u3002\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u89c1[ISO8601](~~25696~~)\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Cpu",
|
||||
"type": "整数",
|
||||
"example": "8",
|
||||
"desc": "vCPU\u6570\u3002"
|
||||
},
|
||||
{
|
||||
"name": "LocalStorageAmount",
|
||||
"type": "整数",
|
||||
"example": "2",
|
||||
"desc": "\u5b9e\u4f8b\u6302\u8f7d\u7684\u672c\u5730\u5b58\u50a8\u6570\u91cf\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ExpiredTime",
|
||||
"type": "文本",
|
||||
"example": "2017-12-10T04:04Z",
|
||||
"desc": "\u8fc7\u671f\u65f6\u95f4\u3002\u4ee5ISO8601\u4e3a\u6807\u51c6\uff0c\u5e76\u4f7f\u7528UTC+0\u65f6\u95f4\uff0c\u683c\u5f0f\u4e3ayyyy-MM-ddTHH:mmZ\u3002\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u89c1[ISO8601](~~25696~~)\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ResourceGroupId",
|
||||
"type": "文本",
|
||||
"example": "rg-bp67acfmxazb4p****",
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5c5e\u7684\u4f01\u4e1a\u8d44\u6e90\u7ec4ID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InternetChargeType",
|
||||
"type": "文本",
|
||||
"example": "PayByTraffic",
|
||||
"desc": "\u7f51\u7edc\u8ba1\u8d39\u7c7b\u578b\u3002\u53ef\u80fd\u503c\uff1a\n\n- PayByBandwidth\uff1a\u6309\u56fa\u5b9a\u5e26\u5bbd\u8ba1\u8d39\u3002\n- PayByTraffic\uff1a\u6309\u4f7f\u7528\u6d41\u91cf\u8ba1\u8d39\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ZoneId",
|
||||
"type": "文本",
|
||||
"example": "cn-hangzhou-g",
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5c5e\u53ef\u7528\u533a\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Recyclable",
|
||||
"type": "boolean",
|
||||
"example": "false",
|
||||
"desc": "\u5b9e\u4f8b\u662f\u5426\u53ef\u4ee5\u56de\u6536\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ISP",
|
||||
"type": "文本",
|
||||
"example": "null",
|
||||
"desc": "> \u8be5\u53c2\u6570\u6b63\u5728\u9080\u6d4b\u4e2d\uff0c\u6682\u672a\u5f00\u653e\u4f7f\u7528\u3002"
|
||||
},
|
||||
{
|
||||
"name": "CreditSpecification",
|
||||
"type": "文本",
|
||||
"example": "Standard",
|
||||
"desc": "\u4fee\u6539\u7a81\u53d1\u6027\u80fd\u5b9e\u4f8b\u7684\u8fd0\u884c\u6a21\u5f0f\u3002\u53ef\u80fd\u503c\uff1a\n\n- Standard\uff1a\u6807\u51c6\u6a21\u5f0f\u3002\u6709\u5173\u5b9e\u4f8b\u6027\u80fd\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u89c1[\u4ec0\u4e48\u662f\u7a81\u53d1\u6027\u80fd\u5b9e\u4f8b](~~59977~~)\u4e2d\u7684\u6027\u80fd\u7ea6\u675f\u6a21\u5f0f\u7ae0\u8282\u3002\n- Unlimited\uff1a\u65e0\u6027\u80fd\u7ea6\u675f\u6a21\u5f0f\uff0c\u6709\u5173\u5b9e\u4f8b\u6027\u80fd\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u89c1[\u4ec0\u4e48\u662f\u7a81\u53d1\u6027\u80fd\u5b9e\u4f8b](~~59977~~)\u4e2d\u7684\u65e0\u6027\u80fd\u7ea6\u675f\u6a21\u5f0f\u7ae0\u8282\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InstanceTypeFamily",
|
||||
"type": "文本",
|
||||
"example": "ecs.g5",
|
||||
"desc": "\u5b9e\u4f8b\u89c4\u683c\u65cf\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OSType",
|
||||
"type": "文本",
|
||||
"example": "linux",
|
||||
"desc": "\u5b9e\u4f8b\u7684\u64cd\u4f5c\u7cfb\u7edf\u7c7b\u578b\uff0c\u5206\u4e3aWindows Server\u548cLinux\u4e24\u79cd\u3002\u53ef\u80fd\u503c\uff1a\n\n- windows\u3002\n- linux\u3002"
|
||||
},
|
||||
{
|
||||
"name": "NetworkInterfaces",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"Type": {
|
||||
"description": "\u5f39\u6027\u7f51\u5361\u7c7b\u578b\u3002\u53ef\u80fd\u503c\uff1a\n- Primary\uff1a\u4e3b\u7f51\u5361\u3002\n- Secondary\uff1a\u8f85\u52a9\u5f39\u6027\u7f51\u5361\u3002",
|
||||
"type": "文本",
|
||||
"example": "Primary"
|
||||
},
|
||||
"MacAddress": {
|
||||
"description": "\u5f39\u6027\u7f51\u5361\u7684MAC\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "00:16:3e:32:b4:**"
|
||||
},
|
||||
"PrimaryIpAddress": {
|
||||
"description": "\u5f39\u6027\u7f51\u5361\u4e3b\u79c1\u6709IP\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "172.17.**.***"
|
||||
},
|
||||
"NetworkInterfaceId": {
|
||||
"description": "\u5f39\u6027\u7f51\u5361\u7684ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "eni-2zeh9atclduxvf1z****"
|
||||
},
|
||||
"PrivateIpSets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"PrivateIpAddress": {
|
||||
"description": "\u5b9e\u4f8b\u7684\u79c1\u7f51IP\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "172.17.**.**"
|
||||
},
|
||||
"Primary": {
|
||||
"description": "\u662f\u5426\u662f\u4e3b\u79c1\u7f51IP\u5730\u5740\u3002",
|
||||
"type": "boolean",
|
||||
"example": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "PrivateIpSet\u7ec4\u6210\u7684\u96c6\u5408\u3002"
|
||||
},
|
||||
"Ipv6Sets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"Ipv6Address": {
|
||||
"description": "\u4e3a\u5f39\u6027\u7f51\u5361\u6307\u5b9a\u7684IPv6\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "2408:4321:180:1701:94c7:bc38:3bfa:***"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "\u4e3a\u5f39\u6027\u7f51\u5361\u5206\u914d\u7684IPv6\u5730\u5740\u96c6\u5408\u3002\u4ec5\u5f53\u8bf7\u6c42\u53c2\u6570`AdditionalAttributes.N`\u53d6\u503c\u4e3a`NETWORK_PRIMARY_ENI_IP`\u65f6\uff0c\u624d\u4f1a\u8fd4\u56de\u8be5\u53c2\u6570\u503c\u3002"
|
||||
},
|
||||
"Ipv4PrefixSets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"Ipv4Prefix": {
|
||||
"description": "IPv4\u524d\u7f00\u3002",
|
||||
"type": "文本",
|
||||
"example": "47.122.*.*/19"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "IPv4\u524d\u7f00\u96c6\u5408\u3002"
|
||||
},
|
||||
"Ipv6PrefixSets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"Ipv6Prefix": {
|
||||
"description": "IPv6\u524d\u7f00\u3002",
|
||||
"type": "文本",
|
||||
"example": "2001:1111:*:*::/64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "IPv6\u524d\u7f00\u96c6\u5408\u3002"
|
||||
}
|
||||
},
|
||||
"description": "\u5b9e\u4f8b\u5305\u542b\u7684\u5f39\u6027\u7f51\u5361\u96c6\u5408\u3002"
|
||||
},
|
||||
"desc": "\u5b9e\u4f8b\u5305\u542b\u7684\u5f39\u6027\u7f51\u5361\u96c6\u5408\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OperationLocks",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"LockMsg": {
|
||||
"description": "\u5b9e\u4f8b\u88ab\u9501\u5b9a\u7684\u63cf\u8ff0\u4fe1\u606f\u3002",
|
||||
"type": "文本",
|
||||
"example": "The specified instance is locked due to financial reason."
|
||||
},
|
||||
"LockReason": {
|
||||
"description": "\u9501\u5b9a\u7c7b\u578b\u3002\u53ef\u80fd\u503c\uff1a\n\n- financial\uff1a\u56e0\u6b20\u8d39\u88ab\u9501\u5b9a\u3002\n- security\uff1a\u56e0\u5b89\u5168\u539f\u56e0\u88ab\u9501\u5b9a\u3002\n- Recycling\uff1a\u62a2\u5360\u5f0f\u5b9e\u4f8b\u7684\u5f85\u91ca\u653e\u9501\u5b9a\u72b6\u6001\u3002\n- dedicatedhostfinancial\uff1a\u56e0\u4e3a\u4e13\u6709\u5bbf\u4e3b\u673a\u6b20\u8d39\u5bfc\u81f4ECS\u5b9e\u4f8b\u88ab\u9501\u5b9a\u3002\n- refunded\uff1a\u56e0\u9000\u6b3e\u88ab\u9501\u5b9a\u3002",
|
||||
"type": "文本",
|
||||
"example": "Recycling"
|
||||
}
|
||||
}
|
||||
},
|
||||
"desc": "\u5b9e\u4f8b\u7684\u9501\u5b9a\u539f\u56e0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"type": "json",
|
||||
"properties": {
|
||||
"TagValue": {
|
||||
"description": "\u5b9e\u4f8b\u7684\u6807\u7b7e\u503c\u3002",
|
||||
"type": "文本",
|
||||
"example": "TestValue"
|
||||
},
|
||||
"TagKey": {
|
||||
"description": "\u5b9e\u4f8b\u7684\u6807\u7b7e\u952e\u3002",
|
||||
"type": "文本",
|
||||
"example": "TestKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"desc": "\u5b9e\u4f8b\u7684\u6807\u7b7e\u96c6\u5408\u3002"
|
||||
},
|
||||
{
|
||||
"name": "RdmaIpAddress",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"description": "HPC\u5b9e\u4f8b\u7684Rdma\u7f51\u7edcIP\u3002",
|
||||
"type": "文本",
|
||||
"example": "10.10.10.102"
|
||||
},
|
||||
"desc": "HPC\u5b9e\u4f8b\u7684Rdma\u7f51\u7edcIP\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "SecurityGroupIds",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"description": "\u5b89\u5168\u7ec4ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "sg-bp67acfmxazb4p****"
|
||||
},
|
||||
"desc": "\u5b9e\u4f8b\u6240\u5c5e\u5b89\u5168\u7ec4ID\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "PublicIpAddress",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"description": "\u5b9e\u4f8b\u516c\u7f51IP\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "121.40.**.**"
|
||||
},
|
||||
"desc": "\u5b9e\u4f8b\u516c\u7f51IP\u5730\u5740\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "InnerIpAddress",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"description": "\u7ecf\u5178\u7f51\u7edc\u7c7b\u578b\u5b9e\u4f8b\u7684\u5185\u7f51IP\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "10.170.**.**"
|
||||
},
|
||||
"desc": "\u7ecf\u5178\u7f51\u7edc\u7c7b\u578b\u5b9e\u4f8b\u7684\u5185\u7f51IP\u5730\u5740\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "VpcAttributes",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"VpcId": {
|
||||
"description": "\u4e13\u6709\u7f51\u7edcVPC ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "vpc-2zeuphj08tt7q3brd****"
|
||||
},
|
||||
"NatIpAddress": {
|
||||
"description": "\u4e91\u4ea7\u54c1\u7684IP\uff0c\u7528\u4e8eVPC\u4e91\u4ea7\u54c1\u4e4b\u95f4\u7684\u7f51\u7edc\u4e92\u901a\u3002",
|
||||
"type": "文本",
|
||||
"example": "172.17.**.**"
|
||||
},
|
||||
"VSwitchId": {
|
||||
"description": "\u865a\u62df\u4ea4\u6362\u673aID\u3002",
|
||||
"type": "文本",
|
||||
"example": "vsw-2zeh0r1pabwtg6wcs****"
|
||||
},
|
||||
"PrivateIpAddress": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "\u79c1\u6709IP\u5730\u5740\u3002",
|
||||
"type": "文本",
|
||||
"example": "172.17.**.**"
|
||||
},
|
||||
"description": "\u79c1\u6709IP\u5730\u5740\u5217\u8868\u3002"
|
||||
}
|
||||
},
|
||||
"desc": "\u4e13\u6709\u7f51\u7edcVPC\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "EipAddress",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"IsSupportUnassociate": {
|
||||
"description": "\u662f\u5426\u53ef\u4ee5\u89e3\u7ed1\u5f39\u6027\u516c\u7f51IP\u3002",
|
||||
"type": "boolean",
|
||||
"example": "true"
|
||||
},
|
||||
"InternetChargeType": {
|
||||
"description": "\u5f39\u6027\u516c\u7f51IP\u7684\u8ba1\u8d39\u65b9\u5f0f\u3002\n\n- PayByBandwidth\uff1a\u6309\u5e26\u5bbd\u8ba1\u8d39\u3002\n\n- PayByTraffic\uff1a\u6309\u6d41\u91cf\u8ba1\u8d39\u3002",
|
||||
"type": "文本",
|
||||
"example": "PayByTraffic"
|
||||
},
|
||||
"IpAddress": {
|
||||
"description": "\u5f39\u6027\u516c\u7f51IP\u3002",
|
||||
"type": "文本",
|
||||
"example": "42.112.**.**"
|
||||
},
|
||||
"Bandwidth": {
|
||||
"description": "\u5f39\u6027\u516c\u7f51IP\u7684\u516c\u7f51\u5e26\u5bbd\u9650\u901f\uff0c\u5355\u4f4d\u4e3aMbit/s\u3002",
|
||||
"type": "整数",
|
||||
"format": "int32",
|
||||
"example": "5"
|
||||
},
|
||||
"AllocationId": {
|
||||
"description": "\u5f39\u6027\u516c\u7f51IP\u7684ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "eip-2ze88m67qx5z****"
|
||||
}
|
||||
},
|
||||
"desc": "\u5f39\u6027\u516c\u7f51IP\u7ed1\u5b9a\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "HibernationOptions",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"Configured": {
|
||||
"description": "> \u8be5\u53c2\u6570\u6b63\u5728\u9080\u6d4b\u4e2d\uff0c\u6682\u672a\u5f00\u653e\u4f7f\u7528\u3002",
|
||||
"type": "boolean",
|
||||
"example": "false"
|
||||
}
|
||||
},
|
||||
"desc": "> \u8be5\u53c2\u6570\u6b63\u5728\u9080\u6d4b\u4e2d\uff0c\u6682\u672a\u5f00\u653e\u4f7f\u7528\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DedicatedHostAttribute",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"DedicatedHostId": {
|
||||
"description": "\u4e13\u6709\u5bbf\u4e3b\u673aID\u3002",
|
||||
"type": "文本",
|
||||
"example": "dh-bp67acfmxazb4p****"
|
||||
},
|
||||
"DedicatedHostName": {
|
||||
"description": "\u4e13\u6709\u5bbf\u4e3b\u673a\u540d\u79f0\u3002",
|
||||
"type": "文本",
|
||||
"example": "testDedicatedHostName"
|
||||
},
|
||||
"DedicatedHostClusterId": {
|
||||
"description": "\u4e13\u6709\u5bbf\u4e3b\u673a\u96c6\u7fa4ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "dc-bp67acfmxazb4h****"
|
||||
}
|
||||
},
|
||||
"desc": "\u7531\u4e13\u6709\u5bbf\u4e3b\u673a\u96c6\u7fa4ID\uff08DedicatedHostClusterId\uff09\u3001\u4e13\u6709\u5bbf\u4e3b\u673aID\uff08DedicatedHostId\uff09\u548c\u540d\u79f0\uff08DedicatedHostName\uff09\u7ec4\u6210\u7684\u5bbf\u4e3b\u673a\u5c5e\u6027\u6570\u7ec4\u3002"
|
||||
},
|
||||
{
|
||||
"name": "EcsCapacityReservationAttr",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"CapacityReservationPreference": {
|
||||
"description": "\u5bb9\u91cf\u9884\u7559\u504f\u597d\u3002",
|
||||
"type": "文本",
|
||||
"example": "cr-bp67acfmxazb4p****"
|
||||
},
|
||||
"CapacityReservationId": {
|
||||
"description": "\u5bb9\u91cf\u9884\u7559ID\u3002",
|
||||
"type": "文本",
|
||||
"example": "cr-bp67acfmxazb4p****"
|
||||
}
|
||||
},
|
||||
"desc": "\u4e91\u670d\u52a1\u5668ECS\u7684\u5bb9\u91cf\u9884\u7559\u76f8\u5173\u53c2\u6570\u3002"
|
||||
},
|
||||
{
|
||||
"name": "DedicatedInstanceAttribute",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"Affinity": {
|
||||
"description": "\u4e13\u6709\u5bbf\u4e3b\u673a\u5b9e\u4f8b\u662f\u5426\u4e0e\u4e13\u6709\u5bbf\u4e3b\u673a\u5173\u8054\u3002\u53ef\u80fd\u503c\uff1a\n\n- default\uff1a\u4e13\u6709\u5bbf\u4e3b\u673a\u5b9e\u4f8b\u4e0d\u4e0e\u4e13\u6709\u5bbf\u4e3b\u673a\u5173\u8054\u3002\u505c\u673a\u4e0d\u6536\u8d39\u5b9e\u4f8b\u91cd\u542f\u540e\uff0c\u53ef\u80fd\u4f1a\u653e\u7f6e\u5728\u81ea\u52a8\u8d44\u6e90\u90e8\u7f72\u6c60\u4e2d\u7684\u5176\u5b83\u4e13\u6709\u5bbf\u4e3b\u673a\u4e0a\u3002\n\n- host\uff1a\u4e13\u6709\u5bbf\u4e3b\u673a\u5b9e\u4f8b\u4e0e\u4e13\u6709\u5bbf\u4e3b\u673a\u5173\u8054\u3002\u505c\u673a\u4e0d\u6536\u8d39\u5b9e\u4f8b\u91cd\u542f\u540e\uff0c\u4ecd\u653e\u7f6e\u5728\u539f\u4e13\u6709\u5bbf\u4e3b\u673a\u4e0a\u3002",
|
||||
"type": "文本",
|
||||
"example": "default"
|
||||
},
|
||||
"Tenancy": {
|
||||
"description": "\u5b9e\u4f8b\u7684\u5bbf\u4e3b\u673a\u7c7b\u578b\u662f\u5426\u4e3a\u4e13\u6709\u5bbf\u4e3b\u673a\u3002\u53ef\u80fd\u503c\uff1a\n\n- default\uff1a\u5b9e\u4f8b\u7684\u5bbf\u4e3b\u673a\u7c7b\u578b\u4e0d\u662f\u4e13\u6709\u5bbf\u4e3b\u673a\u3002\n\n- host\uff1a\u5b9e\u4f8b\u7684\u5bbf\u4e3b\u673a\u7c7b\u578b\u4e3a\u4e13\u6709\u5bbf\u4e3b\u673a\u3002",
|
||||
"type": "文本",
|
||||
"example": "default"
|
||||
}
|
||||
},
|
||||
"desc": "\u4e13\u6709\u5bbf\u4e3b\u673a\u5b9e\u4f8b\u7684\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "CpuOptions",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"Numa": {
|
||||
"description": "\u5206\u914d\u7684\u7ebf\u7a0b\u6570\u3002\u53ef\u80fd\u503c\u4e3a2\u3002",
|
||||
"type": "文本",
|
||||
"example": "2"
|
||||
},
|
||||
"CoreCount": {
|
||||
"description": "\u7269\u7406CPU\u6838\u5fc3\u6570\u3002",
|
||||
"type": "整数",
|
||||
"format": "int32",
|
||||
"example": "2"
|
||||
},
|
||||
"ThreadsPerCore": {
|
||||
"description": "CPU\u7ebf\u7a0b\u6570\u3002",
|
||||
"type": "整数",
|
||||
"format": "int32",
|
||||
"example": "4"
|
||||
}
|
||||
},
|
||||
"desc": "CPU\u914d\u7f6e\u8be6\u60c5\u3002"
|
||||
},
|
||||
{
|
||||
"name": "MetadataOptions",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"HttpEndpoint": {
|
||||
"description": "\u662f\u5426\u542f\u7528\u5b9e\u4f8b\u5143\u6570\u636e\u7684\u8bbf\u95ee\u901a\u9053\u3002\u53ef\u80fd\u503c\uff1a\n- enabled\uff1a\u542f\u7528\u3002\n- disabled\uff1a\u7981\u7528\u3002",
|
||||
"type": "文本",
|
||||
"example": "enabled"
|
||||
},
|
||||
"HttpPutResponseHopLimit": {
|
||||
"description": "> \u8be5\u53c2\u6570\u6682\u672a\u5f00\u653e\u4f7f\u7528\u3002",
|
||||
"type": "整数",
|
||||
"format": "int32",
|
||||
"example": "0"
|
||||
},
|
||||
"HttpTokens": {
|
||||
"description": "\u8bbf\u95ee\u5b9e\u4f8b\u5143\u6570\u636e\u65f6\u662f\u5426\u5f3a\u5236\u4f7f\u7528\u52a0\u56fa\u6a21\u5f0f\uff08IMDSv2\uff09\u3002\u53ef\u80fd\u503c\uff1a\n- optional\uff1a\u4e0d\u5f3a\u5236\u4f7f\u7528\u3002\n- required\uff1a\u5f3a\u5236\u4f7f\u7528\u3002",
|
||||
"type": "文本",
|
||||
"example": "optional"
|
||||
}
|
||||
},
|
||||
"desc": "\u5143\u6570\u636e\u9009\u9879\u96c6\u5408\u3002"
|
||||
},
|
||||
{
|
||||
"name": "ImageOptions",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"LoginAsNonRoot": {
|
||||
"description": "\u4f7f\u7528\u8be5\u955c\u50cf\u7684\u5b9e\u4f8b\u662f\u5426\u652f\u6301\u4f7f\u7528ecs-user\u7528\u6237\u767b\u5f55\u3002\u53ef\u80fd\u503c\uff1a\n\n- true\uff1a\u662f\n\n- false\uff1a\u5426",
|
||||
"type": "boolean",
|
||||
"example": "false"
|
||||
}
|
||||
},
|
||||
"desc": "\u955c\u50cf\u76f8\u5173\u5c5e\u6027\u4fe1\u606f\u3002"
|
||||
}
|
||||
]
|
427
cmdb-api/api/lib/cmdb/auto_discovery/templates/aws_ec2.json
Normal file
427
cmdb-api/api/lib/cmdb/auto_discovery/templates/aws_ec2.json
Normal file
@@ -0,0 +1,427 @@
|
||||
[
|
||||
{
|
||||
"name": "amiLaunchIndex",
|
||||
"type": "整数",
|
||||
"desc": "The AMI launch index, which can be used to find this instance in the launch group.",
|
||||
"example": "0"
|
||||
},
|
||||
{
|
||||
"name": "architecture",
|
||||
"type": "文本",
|
||||
"desc": "The architecture of the image.",
|
||||
"example": "x86_64"
|
||||
},
|
||||
{
|
||||
"name": "blockDeviceMapping",
|
||||
"type": "json",
|
||||
"desc": "Any block device mapping entries for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"deviceName": "/dev/xvda",
|
||||
"ebs": {
|
||||
"volumeId": "vol-1234567890abcdef0",
|
||||
"status": "attached",
|
||||
"attachTime": "2015-12-22T10:44:09.000Z",
|
||||
"deleteOnTermination": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bootMode",
|
||||
"type": "文本",
|
||||
"desc": "The boot mode that was specified by the AMI. If the value is uefi-preferred, the AMI supports both UEFI and Legacy BIOS. The currentInstanceBootMode parameter is the boot mode that is used to boot the instance at launch or start.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the Capacity Reservation.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "capacityReservationSpecification",
|
||||
"type": "json",
|
||||
"desc": "Information about the Capacity Reservation targeting option.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "clientToken",
|
||||
"type": "文本",
|
||||
"desc": "The idempotency token you provided when you launched the instance, if applicable.",
|
||||
"example": "xMcwG14507example"
|
||||
},
|
||||
{
|
||||
"name": "cpuOptions",
|
||||
"type": "json",
|
||||
"desc": "The CPU options for the instance.",
|
||||
"example": {
|
||||
"coreCount": "1",
|
||||
"threadsPerCore": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "currentInstanceBootMode",
|
||||
"type": "文本",
|
||||
"desc": "The boot mode that is used to boot the instance at launch or start. For more information, see Boot modes in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "dnsName",
|
||||
"type": "文本",
|
||||
"desc": "[IPv4 only] The public DNS name assigned to the instance. This name is not available until the instance enters the running state. This name is only available if you've enabled DNS hostnames for your VPC.",
|
||||
"example": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com"
|
||||
},
|
||||
{
|
||||
"name": "ebsOptimized",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether the instance is optimized for Amazon EBS I/O. This optimization provides dedicated throughput to Amazon EBS and an optimized configuration stack to provide optimal I/O performance. This optimization isn't available with all instance types. Additional usage charges apply when using an EBS Optimized instance.",
|
||||
"example": "false"
|
||||
},
|
||||
{
|
||||
"name": "elasticGpuAssociationSet",
|
||||
"type": "json",
|
||||
"desc": "The Elastic GPU associated with the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "elasticInferenceAcceleratorAssociationSet",
|
||||
"type": "json",
|
||||
"desc": "The elastic inference accelerator associated with the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "enaSupport",
|
||||
"type": "Boolean",
|
||||
"desc": "Specifies whether enhanced networking with ENA is enabled.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "enclaveOptions",
|
||||
"type": "json",
|
||||
"desc": "Indicates whether the instance is enabled for AWS Nitro Enclaves.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "groupSet",
|
||||
"type": "json",
|
||||
"desc": "The security groups for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"groupId": "sg-e4076980",
|
||||
"groupName": "SecurityGroup1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hibernationOptions",
|
||||
"type": "json",
|
||||
"desc": "Indicates whether the instance is enabled for hibernation.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "文本",
|
||||
"desc": "The hypervisor type of the instance. The value xen is used for both Xen and Nitro hypervisors.",
|
||||
"example": "xen"
|
||||
},
|
||||
{
|
||||
"name": "iamInstanceProfile",
|
||||
"type": "json",
|
||||
"desc": "The IAM instance profile associated with the instance, if applicable.",
|
||||
"example": {
|
||||
"arn": "arn:aws:iam::123456789012:instance-profile/AdminRole",
|
||||
"id": "ABCAJEDNCAA64SSD123AB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "imageId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the AMI used to launch the instance.",
|
||||
"example": "ami-bff32ccc"
|
||||
},
|
||||
{
|
||||
"name": "instanceId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the instance.",
|
||||
"example": "i-1234567890abcdef0"
|
||||
},
|
||||
{
|
||||
"name": "instanceLifecycle",
|
||||
"type": "文本",
|
||||
"desc": "Indicates whether this is a Spot Instance or a Scheduled Instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "instanceState",
|
||||
"type": "json",
|
||||
"desc": "The current state of the instance.",
|
||||
"example": {
|
||||
"code": "16",
|
||||
"name": "running"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "instanceType",
|
||||
"type": "文本",
|
||||
"desc": "The instance type.",
|
||||
"example": "t2.micro"
|
||||
},
|
||||
{
|
||||
"name": "ipAddress",
|
||||
"type": "文本",
|
||||
"desc": "The public IPv4 address, or the Carrier IP address assigned to the instance, if applicable.",
|
||||
"example": "54.194.252.215"
|
||||
},
|
||||
{
|
||||
"name": "ipv6Address",
|
||||
"type": "文本",
|
||||
"desc": "The IPv6 address assigned to the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "kernelId",
|
||||
"type": "文本",
|
||||
"desc": "The kernel associated with this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "keyName",
|
||||
"type": "文本",
|
||||
"desc": "The name of the key pair, if this instance was launched with an associated key pair.",
|
||||
"example": "my_keypair"
|
||||
},
|
||||
{
|
||||
"name": "launchTime",
|
||||
"type": "Time",
|
||||
"desc": "The time the instance was launched.",
|
||||
"example": "2018-05-08T16:46:19.000Z"
|
||||
},
|
||||
{
|
||||
"name": "licenseSet",
|
||||
"type": "json",
|
||||
"desc": "The license configurations for the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "maintenanceOptions",
|
||||
"type": "json",
|
||||
"desc": "Provides information on the recovery and maintenance options of your instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "metadataOptions",
|
||||
"type": "json",
|
||||
"desc": "The metadata options for the instance.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "monitoring",
|
||||
"type": "json",
|
||||
"desc": "The monitoring for the instance.",
|
||||
"example": {
|
||||
"state": "disabled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "networkInterfaceSet",
|
||||
"type": "json",
|
||||
"desc": "The network interfaces for the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"networkInterfaceId": "eni-551ba033",
|
||||
"subnetId": "subnet-56f5f633",
|
||||
"vpcId": "vpc-11112222",
|
||||
"description": "Primary network interface",
|
||||
"ownerId": "123456789012",
|
||||
"status": "in-use",
|
||||
"macAddress": "02:dd:2c:5e:01:69",
|
||||
"privateIpAddress": "192.168.1.88",
|
||||
"privateDnsName": "ip-192-168-1-88.eu-west-1.compute.internal",
|
||||
"sourceDestCheck": "true",
|
||||
"groupSet": {
|
||||
"item": {
|
||||
"groupId": "sg-e4076980",
|
||||
"groupName": "SecurityGroup1"
|
||||
}
|
||||
},
|
||||
"attachment": {
|
||||
"attachmentId": "eni-attach-39697adc",
|
||||
"deviceIndex": "0",
|
||||
"status": "attached",
|
||||
"attachTime": "2018-05-08T16:46:19.000Z",
|
||||
"deleteOnTermination": "true"
|
||||
},
|
||||
"association": {
|
||||
"publicIp": "54.194.252.215",
|
||||
"publicDnsName": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com",
|
||||
"ipOwnerId": "amazon"
|
||||
},
|
||||
"privateIpAddressesSet": {
|
||||
"item": {
|
||||
"privateIpAddress": "192.168.1.88",
|
||||
"privateDnsName": "ip-192-168-1-88.eu-west-1.compute.internal",
|
||||
"primary": "true",
|
||||
"association": {
|
||||
"publicIp": "54.194.252.215",
|
||||
"publicDnsName": "ec2-54-194-252-215.eu-west-1.compute.amazonaws.com",
|
||||
"ipOwnerId": "amazon"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ipv6AddressesSet": {
|
||||
"item": {
|
||||
"ipv6Address": "2001:db8:1234:1a2b::123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "outpostArn",
|
||||
"type": "文本",
|
||||
"desc": "The Amazon Resource Name (ARN) of the Outpost.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "placement",
|
||||
"type": "json",
|
||||
"desc": "The location where the instance launched, if applicable.",
|
||||
"example": {
|
||||
"availabilityZone": "eu-west-1c",
|
||||
"groupName": null,
|
||||
"tenancy": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"type": "文本",
|
||||
"desc": "The value is Windows for Windows instances; otherwise blank.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "platformDetails",
|
||||
"type": "文本",
|
||||
"desc": "The platform details value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "privateDnsName",
|
||||
"type": "文本",
|
||||
"desc": "[IPv4 only] The private DNS hostname name assigned to the instance. This DNS hostname can only be used inside the Amazon EC2 network. This name is not available until the instance enters the running state.",
|
||||
"example": "ip-192-168-1-88.eu-west-1.compute.internal"
|
||||
},
|
||||
{
|
||||
"name": "privateDnsNameOptions",
|
||||
"type": "json",
|
||||
"desc": "The options for the instance hostname.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "privateIpAddress",
|
||||
"type": "文本",
|
||||
"desc": "The private IPv4 address assigned to the instance.",
|
||||
"example": "192.168.1.88"
|
||||
},
|
||||
{
|
||||
"name": "productCodes",
|
||||
"type": "json",
|
||||
"desc": "The product codes attached to this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "ramdiskId",
|
||||
"type": "文本",
|
||||
"desc": "The RAM disk associated with this instance, if applicable.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"type": "文本",
|
||||
"desc": "The reason for the most recent state transition. This might be an empty string.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceName",
|
||||
"type": "文本",
|
||||
"desc": "The device name of the root device volume (for example, /dev/sda1).",
|
||||
"example": "/dev/xvda"
|
||||
},
|
||||
{
|
||||
"name": "rootDeviceType",
|
||||
"type": "文本",
|
||||
"desc": "The root device type used by the AMI. The AMI can use an EBS volume or an instance store volume.",
|
||||
"example": "ebs"
|
||||
},
|
||||
{
|
||||
"name": "sourceDestCheck",
|
||||
"type": "Boolean",
|
||||
"desc": "Indicates whether source/destination checking is enabled.",
|
||||
"example": "true"
|
||||
},
|
||||
{
|
||||
"name": "spotInstanceRequestId",
|
||||
"type": "文本",
|
||||
"desc": "If the request is a Spot Instance request, the ID of the request.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "sriovNetSupport",
|
||||
"type": "文本",
|
||||
"desc": "Specifies whether enhanced networking with the Intel 82599 Virtual Function interface is enabled.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "stateReason",
|
||||
"type": "json",
|
||||
"desc": "The reason for the most recent state transition.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "subnetId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the subnet in which the instance is running.",
|
||||
"example": "subnet-56f5f633"
|
||||
},
|
||||
{
|
||||
"name": "tagSet",
|
||||
"type": "json",
|
||||
"desc": "Any tags assigned to the instance.",
|
||||
"example": {
|
||||
"item": {
|
||||
"key": "Name",
|
||||
"value": "Server_1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tpmSupport",
|
||||
"type": "文本",
|
||||
"desc": "If the instance is configured for NitroTPM support, the value is v2.0. For more information, see NitroTPM in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "usageOperation",
|
||||
"type": "文本",
|
||||
"desc": "The usage operation value for the instance. For more information, see AMI billing information fields in the Amazon EC2 User Guide.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "usageOperationUpdateTime",
|
||||
"type": "Time",
|
||||
"desc": "The time that the usage operation was last updated.",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "virtualizationType",
|
||||
"type": "文本",
|
||||
"desc": "The virtualization type of the instance.",
|
||||
"example": "hvm"
|
||||
},
|
||||
{
|
||||
"name": "vpcId",
|
||||
"type": "文本",
|
||||
"desc": "The ID of the VPC in which the instance is running.",
|
||||
"example": "vpc-11112222"
|
||||
}
|
||||
]
|
@@ -0,0 +1,292 @@
|
||||
[
|
||||
{
|
||||
"name": "status",
|
||||
"type": "文本",
|
||||
"example": "ACTIVE",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u3002\n\n\u53d6\u503c\u8303\u56f4:\n\nACTIVE\u3001BUILD\u3001DELETED\u3001ERROR\u3001HARD_REBOOT\u3001MIGRATING\u3001PAUSED\u3001REBOOT\u3001REBUILD\u3001RESIZE\u3001REVERT_RESIZE\u3001SHUTOFF\u3001SHELVED\u3001SHELVED_OFFLOADED\u3001SOFT_DELETED\u3001SUSPENDED\u3001VERIFY_RESIZE\n\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u8bf4\u660e\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)"
|
||||
},
|
||||
{
|
||||
"name": "updated",
|
||||
"type": "文本",
|
||||
"example": "2019-05-22T03:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u66f4\u65b0\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "auto_terminate_time",
|
||||
"type": "文本",
|
||||
"example": "2020-01-19T03:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u81ea\u52a8\u91ca\u653e\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2020-01-19T03:30:52Z"
|
||||
},
|
||||
{
|
||||
"name": "hostId",
|
||||
"type": "文本",
|
||||
"example": "c7145889b2e3202cd295ceddb1742ff8941b827b586861fd0acedf64",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u4e3b\u673a\u7684\u4e3b\u673aID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:host",
|
||||
"type": "文本",
|
||||
"example": "pod01.cn-north-1c",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u4e3b\u673a\u7684\u4e3b\u673a\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "addresses",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u7f51\u7edc\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "key_name",
|
||||
"type": "文本",
|
||||
"example": "KeyPair-test",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u4f7f\u7528\u7684\u5bc6\u94a5\u5bf9\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u955c\u50cf\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:task_state",
|
||||
"type": "文本",
|
||||
"example": "rebooting",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5f53\u524d\u4efb\u52a1\u7684\u72b6\u6001\u3002\n\n\u53d6\u503c\u8303\u56f4\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)\u88683\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:vm_state",
|
||||
"type": "文本",
|
||||
"example": "active",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5f53\u524d\u72b6\u6001\u3002\n\n\u4e91\u670d\u52a1\u5668\u72b6\u6001\u8bf4\u660e\u8bf7\u53c2\u8003[\u4e91\u670d\u52a1\u5668\u72b6\u6001](https://support.huaweicloud.com/api-ecs/ecs_08_0002.html)\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:instance_name",
|
||||
"type": "文本",
|
||||
"example": "instance-0048a91b",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u522b\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hypervisor_hostname",
|
||||
"type": "文本",
|
||||
"example": "nova022@36",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u865a\u62df\u5316\u4e3b\u673a\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "flavor",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u89c4\u683c\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "文本",
|
||||
"example": "4f4b3dfa-eb70-47cf-a60a-998a53bd6666",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668ID,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "security_groups",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerSecurityGroup"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u5b89\u5168\u7ec4\u5217\u8868\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-AZ:availability_zone",
|
||||
"type": "文本",
|
||||
"example": "cn-north-1c",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5728\u53ef\u7528\u533a\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "文本",
|
||||
"example": "05498fe56b8010d41f7fc01e280b6666",
|
||||
"desc": "\u521b\u5efa\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u7528\u6237ID,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "文本",
|
||||
"example": "ecs-test-server",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"type": "文本",
|
||||
"example": "2017-07-15T11:30:52Z",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u521b\u5efa\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:19:19Z"
|
||||
},
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"type": "文本",
|
||||
"example": "743b4c0428d94531b9f2add666646666",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u79df\u6237ID,\u5373\u9879\u76eeid,\u548cproject_id\u8868\u793a\u76f8\u540c\u7684\u6982\u5ff5,\u683c\u5f0f\u4e3aUUID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-DCF:diskConfig",
|
||||
"type": "文本",
|
||||
"example": "AUTO",
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027, diskConfig\u7684\u7c7b\u578b\u3002\n\n- MANUAL,\u955c\u50cf\u7a7a\u95f4\u4e0d\u4f1a\u6269\u5c55\u3002\n- AUTO,\u7cfb\u7edf\u76d8\u955c\u50cf\u7a7a\u95f4\u4f1a\u81ea\u52a8\u6269\u5c55\u4e3a\u4e0eflavor\u5927\u5c0f\u4e00\u81f4\u3002"
|
||||
},
|
||||
{
|
||||
"name": "accessIPv4",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u9884\u7559\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "accessIPv6",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u9884\u7559\u5c5e\u6027\u3002"
|
||||
},
|
||||
{
|
||||
"name": "fault",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6545\u969c\u4fe1\u606f\u3002\n\n\u53ef\u9009\u53c2\u6570,\u5728\u5f39\u6027\u4e91\u670d\u52a1\u5668\u72b6\u6001\u4e3aERROR\u4e14\u5b58\u5728\u5f02\u5e38\u7684\u60c5\u51b5\u4e0b\u8fd4\u56de\u3002"
|
||||
},
|
||||
{
|
||||
"name": "progress",
|
||||
"type": "整数",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u8fdb\u5ea6\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-STS:power_state",
|
||||
"type": "整数",
|
||||
"example": 4,
|
||||
"desc": "\u6269\u5c55\u5c5e\u6027,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7535\u6e90\u72b6\u6001\u3002"
|
||||
},
|
||||
{
|
||||
"name": "config_drive",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "config drive\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "metadata",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5143\u6570\u636e\u3002\n\n> \u8bf4\u660e:\n> \n> \u5143\u6570\u636e\u5305\u542b\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u5b57\u6bb5\u548c\u7528\u6237\u8bbe\u7f6e\u7684\u5b57\u6bb5\u3002\n\n\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u5b57\u6bb5\n\n1. charging_mode\n\u4e91\u670d\u52a1\u5668\u7684\u8ba1\u8d39\u7c7b\u578b\u3002\n\n- \u201c0\u201d:\u6309\u9700\u8ba1\u8d39(\u5373postPaid-\u540e\u4ed8\u8d39\u65b9\u5f0f)\u3002\n- \u201c1\u201d:\u6309\u5305\u5e74\u5305\u6708\u8ba1\u8d39(\u5373prePaid-\u9884\u4ed8\u8d39\u65b9\u5f0f)\u3002\"2\":\u7ade\u4ef7\u5b9e\u4f8b\u8ba1\u8d39\n\n2. metering.order_id\n\u6309\u201c\u5305\u5e74/\u5305\u6708\u201d\u8ba1\u8d39\u7684\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8ba2\u5355ID\u3002\n\n3. metering.product_id\n\u6309\u201c\u5305\u5e74/\u5305\u6708\u201d\u8ba1\u8d39\u7684\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u4ea7\u54c1ID\u3002\n\n4. vpc_id\n\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u7684\u865a\u62df\u79c1\u6709\u4e91ID\u3002\n\n5. EcmResStatus\n\u4e91\u670d\u52a1\u5668\u7684\u51bb\u7ed3\u72b6\u6001\u3002\n\n- normal:\u4e91\u670d\u52a1\u5668\u6b63\u5e38\u72b6\u6001(\u672a\u88ab\u51bb\u7ed3)\u3002\n- freeze:\u4e91\u670d\u52a1\u5668\u88ab\u51bb\u7ed3\u3002\n\n> \u5f53\u4e91\u670d\u52a1\u5668\u88ab\u51bb\u7ed3\u6216\u8005\u89e3\u51bb\u540e,\u7cfb\u7edf\u9ed8\u8ba4\u6dfb\u52a0\u8be5\u5b57\u6bb5,\u4e14\u8be5\u5b57\u6bb5\u5fc5\u9009\u3002\n\n6. metering.image_id\n\u4e91\u670d\u52a1\u5668\u64cd\u4f5c\u7cfb\u7edf\u5bf9\u5e94\u7684\u955c\u50cfID\n\n7. metering.imagetype\n\u955c\u50cf\u7c7b\u578b,\u76ee\u524d\u652f\u6301:\n\n- \u516c\u5171\u955c\u50cf(gold)\n- \u79c1\u6709\u955c\u50cf(private)\n- \u5171\u4eab\u955c\u50cf(shared)\n\n8. metering.resourcespeccode\n\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8d44\u6e90\u89c4\u683c\u3002\n\n9. image_name\n\u4e91\u670d\u52a1\u5668\u64cd\u4f5c\u7cfb\u7edf\u5bf9\u5e94\u7684\u955c\u50cf\u540d\u79f0\u3002\n\n10. os_bit\n\u64cd\u4f5c\u7cfb\u7edf\u4f4d\u6570,\u4e00\u822c\u53d6\u503c\u4e3a\u201c32\u201d\u6216\u8005\u201c64\u201d\u3002\n\n11. lockCheckEndpoint\n\u56de\u8c03URL,\u7528\u4e8e\u68c0\u67e5\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u662f\u5426\u6709\u6548\u3002\n\n- \u5982\u679c\u6709\u6548,\u5219\u4e91\u670d\u52a1\u5668\u4fdd\u6301\u9501\u5b9a\u72b6\u6001\u3002\n- \u5982\u679c\u65e0\u6548,\u89e3\u9664\u9501\u5b9a\u72b6\u6001,\u5220\u9664\u5931\u6548\u7684\u9501\u3002\n\n12. lockSource\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6765\u81ea\u54ea\u4e2a\u670d\u52a1\u3002\u8ba2\u5355\u52a0\u9501(ORDER)\n\n13. lockSourceId\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u6765\u81ea\u54ea\u4e2aID\u3002lockSource\u4e3a\u201cORDER\u201d\u65f6,lockSourceId\u4e3a\u8ba2\u5355ID\u3002\n\n14. lockScene\n\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u52a0\u9501\u7c7b\u578b\u3002\n\n- \u6309\u9700\u8f6c\u5305\u5468\u671f(TO_PERIOD_LOCK)\n\n15. virtual_env_type\n\n- IOS\u955c\u50cf\u521b\u5efa\u865a\u62df\u673a,\"virtual_env_type\": \"IsoImage\" \u5c5e\u6027;\n- \u975eIOS\u955c\u50cf\u521b\u5efa\u865a\u62df\u673a,\u572819.5.0\u7248\u672c\u4ee5\u540e\u521b\u5efa\u7684\u865a\u62df\u673a\u5c06\u4e0d\u4f1a\u6dfb\u52a0virtual_env_type \u5c5e\u6027,\u800c\u5728\u6b64\u4e4b\u524d\u7684\u7248\u672c\u521b\u5efa\u7684\u865a\u62df\u673a\u53ef\u80fd\u4f1a\u8fd4\u56de\"virtual_env_type\": \"FusionCompute\"\u5c5e\u6027 \u3002\n\n> virtual_env_type\u5c5e\u6027\u4e0d\u5141\u8bb8\u7528\u6237\u589e\u52a0\u3001\u5220\u9664\u548c\u4fee\u6539\u3002\n\n16. metering.resourcetype\n\u4e91\u670d\u52a1\u5668\u5bf9\u5e94\u7684\u8d44\u6e90\u7c7b\u578b\u3002\n\n17. os_type\n\u64cd\u4f5c\u7cfb\u7edf\u7c7b\u578b,\u53d6\u503c\u4e3a:Linux\u3001Windows\u3002\n\n18. cascaded.instance_extrainfo\n\u7cfb\u7edf\u5185\u90e8\u865a\u62df\u673a\u6269\u5c55\u4fe1\u606f\u3002\n\n19. __support_agent_list\n\u4e91\u670d\u52a1\u5668\u662f\u5426\u652f\u6301\u4f01\u4e1a\u4e3b\u673a\u5b89\u5168\u3001\u4e3b\u673a\u76d1\u63a7\u3002\n\n- \u201chss\u201d:\u4f01\u4e1a\u4e3b\u673a\u5b89\u5168\n- \u201cces\u201d:\u4e3b\u673a\u76d1\u63a7\n\n20. agency_name\n\u59d4\u6258\u7684\u540d\u79f0\u3002\n\n\u59d4\u6258\u662f\u7531\u79df\u6237\u7ba1\u7406\u5458\u5728\u7edf\u4e00\u8eab\u4efd\u8ba4\u8bc1\u670d\u52a1(Identity and Access Management,IAM)\u4e0a\u521b\u5efa\u7684,\u53ef\u4ee5\u4e3a\u5f39\u6027\u4e91\u670d\u52a1\u5668\u63d0\u4f9b\u8bbf\u95ee\u4e91\u670d\u52a1\u7684\u4e34\u65f6\u51ed\u8bc1\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:launched_at",
|
||||
"type": "文本",
|
||||
"example": "2018-08-15T14:21:22.000000",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u542f\u52a8\u65f6\u95f4\u3002\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:23:59.000000"
|
||||
},
|
||||
{
|
||||
"name": "OS-SRV-USG:terminated_at",
|
||||
"type": "文本",
|
||||
"example": "2019-05-22T03:23:59.000000",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u5220\u9664\u65f6\u95f4\u3002\n\n\u65f6\u95f4\u683c\u5f0f\u4f8b\u5982:2019-05-22T03:23:59.000000"
|
||||
},
|
||||
{
|
||||
"name": "os-extended-volumes:volumes_attached",
|
||||
"type": "json",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerExtendVolumeAttachment"
|
||||
},
|
||||
"desc": "\u6302\u8f7d\u5230\u5f39\u6027\u4e91\u670d\u52a1\u5668\u4e0a\u7684\u78c1\u76d8\u3002"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "文本",
|
||||
"example": "ecs description",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u63cf\u8ff0\u4fe1\u606f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "host_status",
|
||||
"type": "文本",
|
||||
"example": "UP",
|
||||
"desc": "nova-compute\u72b6\u6001\u3002\n\n- UP:\u670d\u52a1\u6b63\u5e38\n- UNKNOWN:\u72b6\u6001\u672a\u77e5\n- DOWN:\u670d\u52a1\u5f02\u5e38\n- MAINTENANCE:\u7ef4\u62a4\u72b6\u6001\n- \u7a7a\u5b57\u7b26\u4e32:\u5f39\u6027\u4e91\u670d\u52a1\u5668\u65e0\u4e3b\u673a\u4fe1\u606f"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:hostname",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u4e3b\u673a\u540d\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:reservation_id",
|
||||
"type": "文本",
|
||||
"example": "r-f06p3js8",
|
||||
"desc": "\u6279\u91cf\u521b\u5efa\u573a\u666f,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u9884\u7559ID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:launch_index",
|
||||
"type": "整数",
|
||||
"example": null,
|
||||
"desc": "\u6279\u91cf\u521b\u5efa\u573a\u666f,\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7684\u542f\u52a8\u987a\u5e8f\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:kernel_id",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u82e5\u4f7f\u7528AMI\u683c\u5f0f\u7684\u955c\u50cf,\u5219\u8868\u793akernel image\u7684UUID;\u5426\u5219,\u7559\u7a7a\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:ramdisk_id",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "\u82e5\u4f7f\u7528AMI\u683c\u5f0f\u955c\u50cf,\u5219\u8868\u793aramdisk image\u7684UUID;\u5426\u5219,\u7559\u7a7a\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:root_device_name",
|
||||
"type": "文本",
|
||||
"example": "/dev/vda",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7cfb\u7edf\u76d8\u7684\u8bbe\u5907\u540d\u79f0\u3002"
|
||||
},
|
||||
{
|
||||
"name": "OS-EXT-SRV-ATTR:user_data",
|
||||
"type": "文本",
|
||||
"example": "IyEvYmluL2Jhc2gKZWNobyAncm9vdDokNiRjcGRkSjckWm5WZHNiR253Z0l0SGlxUjZxbWtLTlJaeU9lZUtKd3dPbG9XSFdUeGFzWjA1STYwdnJYRTdTUTZGbEpFbWlXZ21WNGNmZ1pac1laN1BkMTBLRndyeC8nIHwgY2hwYXNzd2Q6666",
|
||||
"desc": "\u521b\u5efa\u5f39\u6027\u4e91\u670d\u52a1\u5668\u65f6\u6307\u5b9a\u7684user_data\u3002"
|
||||
},
|
||||
{
|
||||
"name": "locked",
|
||||
"type": "boolean",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u662f\u5426\u4e3a\u9501\u5b9a\u72b6\u6001\u3002\n\n- true:\u9501\u5b9a\n- false:\u672a\u9501\u5b9a"
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"type": "文本"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6807\u7b7e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "os:scheduler_hints",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u8c03\u5ea6\u4fe1\u606f"
|
||||
},
|
||||
{
|
||||
"name": "enterprise_project_id",
|
||||
"type": "文本",
|
||||
"example": "0",
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u6240\u5c5e\u7684\u4f01\u4e1a\u9879\u76eeID\u3002"
|
||||
},
|
||||
{
|
||||
"name": "sys_tags",
|
||||
"type": "文本、多值",
|
||||
"example": {
|
||||
"$ref": "#/definitions/ServerSystemTag"
|
||||
},
|
||||
"desc": "\u5f39\u6027\u4e91\u670d\u52a1\u5668\u7cfb\u7edf\u6807\u7b7e\u3002"
|
||||
},
|
||||
{
|
||||
"name": "cpu_options",
|
||||
"type": "json",
|
||||
"example": null,
|
||||
"desc": "\u81ea\u5b9a\u4e49CPU\u9009\u9879\u3002"
|
||||
},
|
||||
{
|
||||
"name": "hypervisor",
|
||||
"type": "文本",
|
||||
"example": null,
|
||||
"desc": "hypervisor\u4fe1\u606f\u3002"
|
||||
}
|
||||
]
|
@@ -0,0 +1,37 @@
|
||||
[{
|
||||
"name":"manufacturer",
|
||||
"type": "文本",
|
||||
"example":"HUAWEI Technology Co.,Ltd",
|
||||
"desc":"制造产商"
|
||||
},{
|
||||
"name":"sn",
|
||||
"type": "文本",
|
||||
"example":"102030059898",
|
||||
"desc":"设备序列号"
|
||||
},{
|
||||
"name":"device_name",
|
||||
"type": "文本",
|
||||
"example":"USG6525E",
|
||||
"desc":"设备名称"
|
||||
},{
|
||||
"name":"device_model",
|
||||
"type": "文本",
|
||||
"example":"2011.2.321.1.205",
|
||||
"desc":"设备细分类型 结合相关产商获取相应的产品类型"
|
||||
},{
|
||||
"name":"description",
|
||||
"type": "文本",
|
||||
"example":"Huawei Vwersatile Routing Platform Software",
|
||||
"desc":"设备描述"
|
||||
},{
|
||||
"name":"manager_ip",
|
||||
"type": "文本",
|
||||
"example":"192.168.1.1",
|
||||
"desc":"管理ip"
|
||||
}, {
|
||||
"name":"ips",
|
||||
"type": "文本、多值",
|
||||
"example":"192.168.1.1, 192.168.1.2",
|
||||
"desc":"ips"
|
||||
}
|
||||
]
|
297
cmdb-api/api/lib/cmdb/auto_discovery/templates/tencent_cvm.json
Normal file
297
cmdb-api/api/lib/cmdb/auto_discovery/templates/tencent_cvm.json
Normal file
@@ -0,0 +1,297 @@
|
||||
[
|
||||
{
|
||||
"name": "Placement",
|
||||
"type": "json",
|
||||
"desc": "实例所在的位置。",
|
||||
"example": {
|
||||
"HostId": "host-h3m57oik",
|
||||
"ProjectId": 1174660,
|
||||
"HostIds": [],
|
||||
"Zone": "ap-guangzhou-1",
|
||||
"HostIps": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "InstanceId",
|
||||
"type": "文本",
|
||||
"desc": "实例ID。",
|
||||
"example": "ins-xlsyru2j"
|
||||
},
|
||||
{
|
||||
"name": "InstanceType",
|
||||
"type": "文本",
|
||||
"desc": "实例机型。",
|
||||
"example": "S2.SMALL2"
|
||||
},
|
||||
{
|
||||
"name": "CPU",
|
||||
"type": "整数",
|
||||
"desc": "实例的CPU核数,单位:核。",
|
||||
"example": 1
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"type": "整数",
|
||||
"desc": "实例内存容量,单位:GB。",
|
||||
"example": 1
|
||||
},
|
||||
{
|
||||
"name": "RestrictState",
|
||||
"type": "文本",
|
||||
"desc": "实例业务状态。取值范围: NORMAL:表示正常状态的实例 EXPIRED:表示过期的实例 PROTECTIVELY_ISOLATED:表示被安全隔离的实例。",
|
||||
"example": "PROTECTIVELY_ISOLATED"
|
||||
},
|
||||
{
|
||||
"name": "InstanceName",
|
||||
"type": "文本",
|
||||
"desc": "实例名称。",
|
||||
"example": "test"
|
||||
},
|
||||
{
|
||||
"name": "InstanceChargeType",
|
||||
"type": "文本",
|
||||
"desc": "实例计费模式。取值范围: PREPAID:表示预付费,即包年包月 POSTPAID_BY_HOUR:表示后付费,即按量计费 CDHPAID:专用宿主机付费,即只对专用宿主机计费,不对专用宿主机上的实例计费。 SPOTPAID:表示竞价实例付费。",
|
||||
"example": "POSTPAID_BY_HOUR"
|
||||
},
|
||||
{
|
||||
"name": "SystemDisk",
|
||||
"type": "json",
|
||||
"desc": "实例系统盘信息。",
|
||||
"example": {
|
||||
"DiskSize": 50,
|
||||
"CdcId": null,
|
||||
"DiskId": "disk-czsodtl1",
|
||||
"DiskType": "CLOUD_SSD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DataDisks",
|
||||
"type": "json",
|
||||
"desc": "实例数据盘信息。",
|
||||
"example": [
|
||||
{
|
||||
"DeleteWithInstance": true,
|
||||
"Encrypt": true,
|
||||
"CdcId": null,
|
||||
"DiskType": "CLOUD_SSD",
|
||||
"ThroughputPerformance": 0,
|
||||
"KmsKeyId": null,
|
||||
"DiskSize": 50,
|
||||
"SnapshotId": null,
|
||||
"DiskId": "disk-bzsodtn1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PrivateIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例主网卡的内网IP列表。",
|
||||
"example": [
|
||||
"172.16.32.78"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PublicIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例主网卡的公网IP列表。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": [
|
||||
"123.207.11.190"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "InternetAccessible",
|
||||
"type": "json",
|
||||
"desc": "实例带宽信息。",
|
||||
"example": {
|
||||
"PublicIpAssigned": true,
|
||||
"InternetChargeType": "TRAFFIC_POSTPAID_BY_HOUR",
|
||||
"BandwidthPackageId": null,
|
||||
"InternetMaxBandwidthOut": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "VirtualPrivateCloud",
|
||||
"type": "json",
|
||||
"desc": "实例所属虚拟私有网络信息。",
|
||||
"example": {
|
||||
"SubnetId": "subnet-mv4sn55k",
|
||||
"AsVpcGateway": false,
|
||||
"Ipv6AddressCount": 1,
|
||||
"VpcId": "vpc-m0cnatxj",
|
||||
"PrivateIpAddresses": [
|
||||
"172.16.3.59"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ImageId",
|
||||
"type": "文本",
|
||||
"desc": "生产实例所使用的镜像ID。",
|
||||
"example": "img-8toqc6s3"
|
||||
},
|
||||
{
|
||||
"name": "RenewFlag",
|
||||
"type": "文本",
|
||||
"desc": "自动续费标识。取值范围: NOTIFY_AND_MANUAL_RENEW:表示通知即将过期,但不自动续费 NOTIFY_AND_AUTO_RENEW:表示通知即将过期,而且自动续费 DISABLE_NOTIFY_AND_MANUAL_RENEW:表示不通知即将过期,也不自动续费。 注意:后付费模式本项为null",
|
||||
"example": "NOTIFY_AND_MANUAL_RENEW"
|
||||
},
|
||||
{
|
||||
"name": "CreatedTime",
|
||||
"type": "json",
|
||||
"desc": "创建时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。",
|
||||
"example": "2020-09-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ExpiredTime",
|
||||
"type": "json",
|
||||
"desc": "到期时间。按照ISO8601标准表示,并且使用UTC时间。格式为:YYYY-MM-DDThh:mm:ssZ。注意:后付费模式本项为null",
|
||||
"example": "2020-09-22T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "OsName",
|
||||
"type": "文本",
|
||||
"desc": "操作系统名称。",
|
||||
"example": "CentOS 7.4 64bit"
|
||||
},
|
||||
{
|
||||
"name": "SecurityGroupIds",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例所属安全组。该参数可以通过调用 DescribeSecurityGroups 的返回值中的sgId字段来获取。",
|
||||
"example": [
|
||||
"sg-p1ezv4wz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LoginSettings",
|
||||
"type": "json",
|
||||
"desc": "实例登录设置。目前只返回实例所关联的密钥。",
|
||||
"example": {
|
||||
"Password": "123qwe!@#QWE",
|
||||
"KeepImageLogin": "False",
|
||||
"KeyIds": [
|
||||
"skey-b4vakk62"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "InstanceState",
|
||||
"type": "文本",
|
||||
"desc": "实例状态。取值范围: PENDING:表示创建中 LAUNCH_FAILED:表示创建失败 RUNNING:表示运行中 STOPPED:表示关机 STARTING:表示开机中 STOPPING:表示关机中 REBOOTING:表示重启中 SHUTDOWN:表示停止待销毁 TERMINATING:表示销毁中。",
|
||||
"example": "RUNNING"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"type": "json",
|
||||
"desc": "实例关联的标签列表。",
|
||||
"example": [
|
||||
{
|
||||
"Value": "test",
|
||||
"Key": "test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StopChargingMode",
|
||||
"type": "文本",
|
||||
"desc": "实例的关机计费模式。 取值范围: KEEP_CHARGING:关机继续收费 STOP_CHARGING:关机停止收费NOT_APPLICABLE:实例处于非关机状态或者不适用关机停止计费的条件",
|
||||
"example": "NOT_APPLICABLE"
|
||||
},
|
||||
{
|
||||
"name": "Uuid",
|
||||
"type": "文本",
|
||||
"desc": "实例全局唯一ID",
|
||||
"example": "e85f1388-0422-410d-8e50-bef540e78c18"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperation",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作。例:StopInstances、ResetInstance。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "ResetInstancesType"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationState",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作状态。取值范围: SUCCESS:表示操作成功 OPERATING:表示操作执行中 FAILED:表示操作失败 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "SUCCESS"
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationRequestId",
|
||||
"type": "文本",
|
||||
"desc": "实例最新操作的唯一请求 ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "c7de1287-061d-4ace-8caf-6ad8e5a2f29a"
|
||||
},
|
||||
{
|
||||
"name": "DisasterRecoverGroupId",
|
||||
"type": "文本",
|
||||
"desc": "分散置放群组ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "IPv6Addresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "实例的IPv6地址。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": [
|
||||
"2001:0db8:86a3:08d3:1319:8a2e:0370:7344"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CamRoleName",
|
||||
"type": "文本",
|
||||
"desc": "CAM角色名。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "HpcClusterId",
|
||||
"type": "文本",
|
||||
"desc": "高性能计算集群ID。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "RdmaIpAddresses",
|
||||
"type": "文本、多值",
|
||||
"desc": "高性能计算集群IP列表。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": []
|
||||
},
|
||||
{
|
||||
"name": "IsolatedSource",
|
||||
"type": "文本",
|
||||
"desc": "实例隔离类型。取值范围: ARREAR:表示欠费隔离 EXPIRE:表示到期隔离 MANMADE:表示主动退还隔离 NOTISOLATED:表示未隔离 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": "NOTISOLATED"
|
||||
},
|
||||
{
|
||||
"name": "GPUInfo",
|
||||
"type": "json",
|
||||
"desc": "GPU信息。如果是gpu类型子机,该值会返回GPU信息,如果是其他类型子机则不返回。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "LicenseType",
|
||||
"type": "文本",
|
||||
"desc": "实例的操作系统许可类型,默认为TencentCloud",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DisableApiTermination",
|
||||
"type": "Boolean",
|
||||
"desc": "实例销毁保护标志,表示是否允许通过api接口删除实例。取值范围: TRUE:表示开启实例保护,不允许通过api接口删除实例 FALSE:表示关闭实例保护,允许通过api接口删除实例 默认取值:FALSE。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginUser",
|
||||
"type": "文本",
|
||||
"desc": "默认登录用户。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "DefaultLoginPort",
|
||||
"type": "整数",
|
||||
"desc": "默认登录端口。",
|
||||
"example": null
|
||||
},
|
||||
{
|
||||
"name": "LatestOperationErrorMsg",
|
||||
"type": "文本",
|
||||
"desc": "实例的最新操作错误信息。 注意:此字段可能返回 null,表示取不到有效值。",
|
||||
"example": null
|
||||
}
|
||||
]
|
@@ -2,8 +2,14 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import requests
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.custom_dashboard import CustomDashboardManager
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import RelationType
|
||||
@@ -115,6 +121,9 @@ class CITypeAttributesCache(object):
|
||||
PREFIX_ID = "CITypeAttributes::TypeID::{0}"
|
||||
PREFIX_NAME = "CITypeAttributes::TypeName::{0}"
|
||||
|
||||
PREFIX_ID2 = "CITypeAttributes2::TypeID::{0}"
|
||||
PREFIX_NAME2 = "CITypeAttributes2::TypeName::{0}"
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
@@ -132,6 +141,29 @@ class CITypeAttributesCache(object):
|
||||
cls.set(key, attrs)
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
def get2(cls, key):
|
||||
"""
|
||||
return [(type_attr, attr), ]
|
||||
:param key:
|
||||
:return:
|
||||
"""
|
||||
if key is None:
|
||||
return
|
||||
|
||||
attrs = cache.get(cls.PREFIX_NAME2.format(key))
|
||||
attrs = attrs or cache.get(cls.PREFIX_ID2.format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
if attrs is not None:
|
||||
attrs = [(i, AttributeCache.get(i.attr_id)) for i in attrs]
|
||||
cls.set2(key, attrs)
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, values):
|
||||
ci_type = CITypeCache.get(key)
|
||||
@@ -139,6 +171,13 @@ class CITypeAttributesCache(object):
|
||||
cache.set(cls.PREFIX_ID.format(ci_type.id), values)
|
||||
cache.set(cls.PREFIX_NAME.format(ci_type.name), values)
|
||||
|
||||
@classmethod
|
||||
def set2(cls, key, values):
|
||||
ci_type = CITypeCache.get(key)
|
||||
if ci_type is not None:
|
||||
cache.set(cls.PREFIX_ID2.format(ci_type.id), values)
|
||||
cache.set(cls.PREFIX_NAME2.format(ci_type.name), values)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ci_type = CITypeCache.get(key)
|
||||
@@ -147,6 +186,11 @@ class CITypeAttributesCache(object):
|
||||
cache.delete(cls.PREFIX_ID.format(ci_type.id))
|
||||
cache.delete(cls.PREFIX_NAME.format(ci_type.name))
|
||||
|
||||
attrs2 = cls.get2(key)
|
||||
if attrs2 is not None and ci_type:
|
||||
cache.delete(cls.PREFIX_ID2.format(ci_type.id))
|
||||
cache.delete(cls.PREFIX_NAME2.format(ci_type.name))
|
||||
|
||||
|
||||
class CITypeAttributeCache(object):
|
||||
"""
|
||||
@@ -173,3 +217,99 @@ class CITypeAttributeCache(object):
|
||||
@classmethod
|
||||
def clean(cls, type_id, attr_id):
|
||||
cache.delete(cls.PREFIX_ID.format(type_id, attr_id))
|
||||
|
||||
|
||||
class CMDBCounterCache(object):
|
||||
KEY = 'CMDB::Counter'
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
result = cache.get(cls.KEY) or {}
|
||||
|
||||
if not result:
|
||||
result = cls.reset()
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def set(cls, result):
|
||||
cache.set(cls.KEY, result, timeout=0)
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
customs = CustomDashboardManager.get()
|
||||
result = {}
|
||||
for custom in customs:
|
||||
if custom['category'] == 0:
|
||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
||||
elif custom['category'] == 1:
|
||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
||||
elif custom['category'] == 2:
|
||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
||||
|
||||
cls.set(result)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update(cls, custom):
|
||||
result = cache.get(cls.KEY) or {}
|
||||
if not result:
|
||||
result = cls.reset()
|
||||
|
||||
if custom['category'] == 0:
|
||||
result[custom['id']] = cls.summary_counter(custom['type_id'])
|
||||
elif custom['category'] == 1:
|
||||
result[custom['id']] = cls.attribute_counter(custom['type_id'], custom['attr_id'])
|
||||
elif custom['category'] == 2:
|
||||
result[custom['id']] = cls.relation_counter(custom['type_id'], custom['level'])
|
||||
|
||||
cls.set(result)
|
||||
|
||||
@staticmethod
|
||||
def summary_counter(type_id):
|
||||
return db.session.query(CI.id).filter(CI.deleted.is_(False)).filter(CI.type_id == type_id).count()
|
||||
|
||||
@staticmethod
|
||||
def relation_counter(type_id, level):
|
||||
|
||||
uri = current_app.config.get('CMDB_API')
|
||||
|
||||
type_names = requests.get("{}/ci/s?q=_type:{}&count=10000".format(uri, type_id)).json().get('result')
|
||||
type_id_names = [(str(i.get('_id')), i.get(i.get('unique'))) for i in type_names]
|
||||
|
||||
url = "{}/ci_relations/statistics?root_ids={}&level={}".format(
|
||||
uri, ','.join([i[0] for i in type_id_names]), level)
|
||||
stats = requests.get(url).json()
|
||||
|
||||
id2name = dict(type_id_names)
|
||||
type_ids = set()
|
||||
for i in (stats.get('detail') or []):
|
||||
for j in stats['detail'][i]:
|
||||
type_ids.add(j)
|
||||
|
||||
for type_id in type_ids:
|
||||
_type = CITypeCache.get(type_id)
|
||||
id2name[type_id] = _type and _type.alias
|
||||
|
||||
result = dict(summary={}, detail={})
|
||||
for i in stats:
|
||||
if i == "detail":
|
||||
for j in stats['detail']:
|
||||
if id2name[j]:
|
||||
result['detail'][id2name[j]] = stats['detail'][j]
|
||||
result['detail'][id2name[j]] = dict()
|
||||
for _j in stats['detail'][j]:
|
||||
result['detail'][id2name[j]][id2name[_j]] = stats['detail'][j][_j]
|
||||
elif id2name.get(i):
|
||||
result['summary'][id2name[i]] = stats[i]
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def attribute_counter(type_id, attr_id):
|
||||
uri = current_app.config.get('CMDB_API')
|
||||
url = "{}/ci/s?q=_type:{}&fl={}&facet={}".format(uri, type_id, attr_id, attr_id)
|
||||
res = requests.get(url).json()
|
||||
if res.get('facet'):
|
||||
return dict([i[:2] for i in list(res.get('facet').values())[0]])
|
||||
|
@@ -1,32 +1,44 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeUniqueConstraintManager
|
||||
from api.lib.cmdb.const import AttributeDefaultValueEnum
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ConstraintEnum
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import REDIS_PREFIX_CI
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CIRelationHistoryManager
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_IDS
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.lib.utils import Lock
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
@@ -45,27 +57,36 @@ class CIManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(ci_id):
|
||||
return CI.get_by_id(ci_id)
|
||||
|
||||
@staticmethod
|
||||
def get_type_name(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
return CITypeCache.get(ci.type_id).name
|
||||
|
||||
@staticmethod
|
||||
def get_type(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
return CITypeCache.get(ci.type_id)
|
||||
|
||||
@staticmethod
|
||||
def confirm_ci_existed(ci_id):
|
||||
return CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
return CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
@classmethod
|
||||
def get_ci_by_id(cls, ci_id, ret_key=RetKey.NAME, fields=None, need_children=True):
|
||||
"""
|
||||
|
||||
:param ci_id:
|
||||
|
||||
:param ci_id:
|
||||
:param ret_key: name, id, or alias
|
||||
:param fields: attribute list
|
||||
:param need_children:
|
||||
:return:
|
||||
:param need_children:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
res = dict()
|
||||
|
||||
@@ -83,19 +104,63 @@ class CIManager(object):
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_ci_by_id_from_db(ci_id, ret_key=RetKey.NAME, fields=None, need_children=True, use_master=False):
|
||||
"""
|
||||
|
||||
:param ci_id:
|
||||
:param ret_key: name, id or alias
|
||||
:param fields: list
|
||||
:param need_children:
|
||||
:param use_master: whether to use master db
|
||||
:return:
|
||||
@classmethod
|
||||
def valid_ci_only_read(cls, ci):
|
||||
if is_app_admin("cmdb"):
|
||||
return
|
||||
|
||||
validate_permission(CIManager.get_type_name(ci.id), ResourceTypeEnum.CI, PermEnum.READ, "cmdb")
|
||||
|
||||
acl = ACLManager('cmdb')
|
||||
res = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
if res and ci.type_id in CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res]))):
|
||||
return abort(403, ErrFormat.no_permission2)
|
||||
|
||||
@classmethod
|
||||
def _valid_ci_for_no_read(cls, ci, ci_type=None):
|
||||
type_id = ci.type_id if ci else ci_type.id
|
||||
|
||||
acl = ACLManager('cmdb')
|
||||
res = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
type2filters = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res])), type_id=type_id)
|
||||
if res and type_id in type2filters:
|
||||
if type2filters[type_id].get('ci_filter') and ci:
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci import search
|
||||
|
||||
query = "_id:{},{}".format(ci.id, type2filters[type_id].get('ci_filter'))
|
||||
s = search(query)
|
||||
try:
|
||||
response, _, _, _, _, _ = s.search()
|
||||
except SearchError as e:
|
||||
current_app.logger.warning(e)
|
||||
return abort(400, str(e))
|
||||
|
||||
if not response:
|
||||
return abort(403, ErrFormat.ci_filter_perm_ci_no_permission)
|
||||
|
||||
return type2filters[type_id].get('attr_filter') or []
|
||||
|
||||
@classmethod
|
||||
def get_ci_by_id_from_db(cls, ci_id, ret_key=RetKey.NAME, fields=None, need_children=True, use_master=False,
|
||||
valid=False):
|
||||
"""
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
:param ci_id:
|
||||
:param ret_key: name, id or alias
|
||||
:param fields: list
|
||||
:param need_children:
|
||||
:param use_master: whether to use master db
|
||||
:param valid:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
if valid:
|
||||
cls.valid_ci_only_read(ci)
|
||||
|
||||
res = dict()
|
||||
|
||||
@@ -115,8 +180,9 @@ class CIManager(object):
|
||||
use_master=use_master)
|
||||
res.update(_res)
|
||||
|
||||
res['type_id'] = ci_type.id
|
||||
res['ci_id'] = ci_id
|
||||
res['_type'] = ci_type.id
|
||||
res['ci_type_alias'] = ci_type.alias
|
||||
res['_id'] = ci_id
|
||||
|
||||
return res
|
||||
|
||||
@@ -134,19 +200,33 @@ class CIManager(object):
|
||||
|
||||
return numfound, page, res
|
||||
|
||||
@classmethod
|
||||
def get_ad_statistics(cls):
|
||||
res = CI.get_by(to_dict=False)
|
||||
result = dict()
|
||||
for i in res:
|
||||
result.setdefault(i.type_id, dict(total=0, auto_discovery=0))
|
||||
result[i.type_id]['total'] += 1
|
||||
if i.is_auto_discovery:
|
||||
result[i.type_id]['auto_discovery'] += 1
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value):
|
||||
def ci_is_exist(unique_key, unique_value, type_id):
|
||||
"""
|
||||
|
||||
|
||||
:param unique_key: is a attribute
|
||||
:param unique_value:
|
||||
:return:
|
||||
:param unique_value:
|
||||
:param type_id:
|
||||
:return:
|
||||
"""
|
||||
value_table = TableMap(attr_name=unique_key.name).table
|
||||
unique = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
value_table = TableMap(attr=unique_key).table
|
||||
|
||||
unique = db.session.query(value_table).join(CI, CI.id == value_table.ci_id).filter(
|
||||
value_table.attr_id == unique_key.id).filter(value_table.value == unique_value).filter(
|
||||
CI.type_id == type_id).filter(CI.deleted.is_(False)).filter(value_table.deleted.is_(False)).first()
|
||||
|
||||
if unique:
|
||||
return CI.get_by_id(unique.ci_id)
|
||||
|
||||
@@ -155,82 +235,218 @@ class CIManager(object):
|
||||
ci = CI.get_by_id(ci_id)
|
||||
ci.delete() # TODO: soft delete
|
||||
|
||||
@staticmethod
|
||||
def _valid_unique_constraint(type_id, ci_dict, ci_id=None):
|
||||
unique_constraints = CITypeUniqueConstraintManager.get_by_type_id(type_id)
|
||||
if not unique_constraints:
|
||||
return
|
||||
|
||||
attr_ids = []
|
||||
for i in unique_constraints:
|
||||
attr_ids.extend(i.attr_ids)
|
||||
|
||||
attrs = [AttributeCache.get(i) for i in list(set(attr_ids))]
|
||||
id2name = {i.id: i.name for i in attrs if i}
|
||||
not_existed_fields = list(set(id2name.values()) - set(ci_dict.keys()))
|
||||
if not_existed_fields and ci_id is not None:
|
||||
ci_dict = copy.deepcopy(ci_dict)
|
||||
ci_dict.update(AttributeValueManager().get_attr_values(not_existed_fields, ci_id))
|
||||
|
||||
for constraint in unique_constraints:
|
||||
ci_ids = None
|
||||
for attr_id in constraint.attr_ids:
|
||||
value_table = TableMap(attr_name=id2name[attr_id]).table
|
||||
|
||||
_ci_ids = set([i.ci_id for i in value_table.get_by(attr_id=attr_id,
|
||||
to_dict=False,
|
||||
value=ci_dict.get(id2name[attr_id]) or None)])
|
||||
if ci_ids is None:
|
||||
ci_ids = _ci_ids
|
||||
else:
|
||||
ci_ids &= _ci_ids
|
||||
|
||||
if ci_ids - (ci_id and {ci_id} or set()):
|
||||
return abort(400, ErrFormat.unique_constraint.format(
|
||||
" - ".join([id2name[i] for i in constraint.attr_ids])))
|
||||
|
||||
@staticmethod
|
||||
def _auto_inc_id(attr):
|
||||
db.session.remove()
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
with Lock("auto_inc_id_{}".format(attr.name), need_lock=True):
|
||||
max_v = value_table.get_by(attr_id=attr.id, only_query=True).order_by(
|
||||
getattr(value_table, 'value').desc()).first()
|
||||
if max_v is not None:
|
||||
return int(max_v.value) + 1
|
||||
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def add(cls, ci_type_name, exist_policy=ExistPolicy.REPLACE, _no_attribute_policy=ExistPolicy.IGNORE, **ci_dict):
|
||||
def add(cls, ci_type_name,
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=ExistPolicy.IGNORE,
|
||||
is_auto_discovery=False,
|
||||
_is_admin=False,
|
||||
**ci_dict):
|
||||
"""
|
||||
|
||||
:param ci_type_name:
|
||||
|
||||
:param ci_type_name:
|
||||
:param exist_policy: replace or reject or need
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param ci_dict:
|
||||
:return:
|
||||
:param is_auto_discovery: default is False
|
||||
:param _is_admin: default is False
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(400, 'illegality unique attribute')
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(
|
||||
400, ErrFormat.unique_value_not_found.format("unique_id={}".format(ci_type.unique_id)))
|
||||
|
||||
unique_value = ci_dict.get(unique_key.name)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.alias)
|
||||
unique_value = unique_value or ci_dict.get(unique_key.id)
|
||||
unique_value = unique_value or abort(400, '{0} missing'.format(unique_key.name))
|
||||
unique_value = unique_value or abort(400, ErrFormat.unique_key_required.format(unique_key.name))
|
||||
|
||||
existed = cls.ci_is_exist(unique_key, unique_value)
|
||||
if existed is not None:
|
||||
if exist_policy == ExistPolicy.REJECT:
|
||||
return abort(400, 'CI is already existed')
|
||||
if existed.type_id != ci_type.id:
|
||||
existed.update(type_id=ci_type.id)
|
||||
ci = existed
|
||||
else:
|
||||
if exist_policy == ExistPolicy.NEED:
|
||||
return abort(404, 'CI <{0}> does not exist'.format(unique_value))
|
||||
ci = CI.create(type_id=ci_type.id)
|
||||
attrs = CITypeAttributesCache.get2(ci_type_name)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_type_attrs_alias = {attr.alias: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
|
||||
ci = None
|
||||
need_lock = g.user.username not in ("worker", "cmdb_agent", "agent")
|
||||
with Lock(ci_type_name, need_lock=need_lock):
|
||||
existed = cls.ci_is_exist(unique_key, unique_value, ci_type.id)
|
||||
if existed is not None:
|
||||
if exist_policy == ExistPolicy.REJECT:
|
||||
return abort(400, ErrFormat.ci_is_already_existed)
|
||||
|
||||
if existed.type_id != ci_type.id:
|
||||
existed.update(type_id=ci_type.id)
|
||||
ci = existed
|
||||
else:
|
||||
if exist_policy == ExistPolicy.NEED:
|
||||
return abort(404, ErrFormat.ci_not_found.format("{}={}".format(unique_key.name, unique_value)))
|
||||
|
||||
from api.lib.cmdb.const import L_CI
|
||||
if L_CI and len(CI.get_by(type_id=ci_type.id)) > L_CI * 2:
|
||||
return abort(400, ErrFormat.limit_ci.format(L_CI * 2))
|
||||
|
||||
limit_attrs = cls._valid_ci_for_no_read(ci, ci_type) if not _is_admin else {}
|
||||
|
||||
if existed is None: # set default
|
||||
for type_attr, attr in attrs:
|
||||
if attr.default and attr.default.get('default') is not None:
|
||||
if attr.default.get('default') and attr.default.get('default') in (
|
||||
AttributeDefaultValueEnum.CREATED_AT, AttributeDefaultValueEnum.UPDATED_AT):
|
||||
ci_dict[attr.name] = now
|
||||
elif attr.default.get('default') == AttributeDefaultValueEnum.AUTO_INC_ID:
|
||||
ci_dict[attr.name] = cls._auto_inc_id(attr)
|
||||
elif ((attr.name not in ci_dict and attr.alias not in ci_dict) or (
|
||||
ci_dict.get(attr.name) is None and ci_dict.get(attr.alias) is None)):
|
||||
ci_dict[attr.name] = attr.default.get('default')
|
||||
|
||||
if type_attr.is_required and (attr.name not in ci_dict and attr.alias not in ci_dict):
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.name))
|
||||
else:
|
||||
for type_attr, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
computed_attrs = [attr.to_dict() for _, attr in attrs if attr.is_computed] or None
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
|
||||
cls._valid_unique_constraint(ci_type.id, ci_dict, ci and ci.id)
|
||||
|
||||
for k in ci_dict:
|
||||
if k not in ci_type_attrs_name and k not in ci_type_attrs_alias and \
|
||||
_no_attribute_policy == ExistPolicy.REJECT:
|
||||
return abort(400, ErrFormat.attribute_not_found.format(k))
|
||||
|
||||
if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and \
|
||||
ci_type_attrs_alias.get(k) not in limit_attrs:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name or k in ci_type_attrs_alias}
|
||||
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci_type.id, ci and ci.id,
|
||||
ci_type_attrs_name, ci_type_attrs_alias, ci_attr2type_attr)
|
||||
|
||||
ci_type_attrs_name = [attr["name"] for attr in CITypeAttributeManager().get_attributes_by_type_id(ci_type.id)]
|
||||
value_manager = AttributeValueManager()
|
||||
for p, v in ci_dict.items():
|
||||
if p not in ci_type_attrs_name:
|
||||
current_app.logger.warning('ci_type: {0} not has attribute {1}, please check!'.format(ci_type_name, p))
|
||||
continue
|
||||
try:
|
||||
value_manager.create_or_update_attr_value(p, v, ci, _no_attribute_policy)
|
||||
ci = ci or CI.create(type_id=ci_type.id, is_auto_discovery=is_auto_discovery)
|
||||
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr)
|
||||
except BadRequest as e:
|
||||
if existed is None:
|
||||
cls.delete(ci.id)
|
||||
raise e
|
||||
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, **ci_dict):
|
||||
def update(self, ci_id, _is_admin=False, **ci_dict):
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
ci = self.confirm_ci_existed(ci_id)
|
||||
|
||||
ci_type_attrs_name = [attr["name"] for attr in CITypeAttributeManager().get_attributes_by_type_id(ci.type_id)]
|
||||
|
||||
attrs = CITypeAttributesCache.get2(ci.type_id)
|
||||
ci_type_attrs_name = {attr.name: attr for _, attr in attrs}
|
||||
ci_attr2type_attr = {type_attr.attr_id: type_attr for type_attr, _ in attrs}
|
||||
for _, attr in attrs:
|
||||
if attr.default and attr.default.get('default') == AttributeDefaultValueEnum.UPDATED_AT:
|
||||
ci_dict[attr.name] = now
|
||||
|
||||
computed_attrs = [attr.to_dict() for _, attr in attrs if attr.is_computed] or None
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
for p, v in ci_dict.items():
|
||||
if p not in ci_type_attrs_name:
|
||||
current_app.logger.warning('ci_type: {0} not has attribute {1}, please check!'.format(ci.type_id, p))
|
||||
continue
|
||||
|
||||
if computed_attrs:
|
||||
value_manager.handle_ci_compute_attributes(ci_dict, computed_attrs, ci)
|
||||
|
||||
limit_attrs = self._valid_ci_for_no_read(ci) if not _is_admin else {}
|
||||
|
||||
need_lock = g.user.username not in ("worker", "cmdb_agent", "agent")
|
||||
with Lock(ci.ci_type.name, need_lock=need_lock):
|
||||
self._valid_unique_constraint(ci.type_id, ci_dict, ci_id)
|
||||
|
||||
ci_dict = {k: v for k, v in ci_dict.items() if k in ci_type_attrs_name}
|
||||
key2attr = value_manager.valid_attr_value(ci_dict, ci.type_id, ci.id, ci_type_attrs_name,
|
||||
ci_attr2type_attr=ci_attr2type_attr)
|
||||
if limit_attrs:
|
||||
for k in ci_dict:
|
||||
if k not in limit_attrs:
|
||||
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k))
|
||||
|
||||
try:
|
||||
value_manager.create_or_update_attr_value(p, v, ci)
|
||||
record_id = value_manager.create_or_update_attr_value2(ci, ci_dict, key2attr)
|
||||
except BadRequest as e:
|
||||
raise e
|
||||
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
if record_id: # has change
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id))
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci)
|
||||
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def delete(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id))
|
||||
@classmethod
|
||||
def delete(cls, ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(ci_id)))
|
||||
|
||||
cls._valid_ci_for_no_read(ci)
|
||||
|
||||
ci_dict = cls.get_cis_by_ids([ci_id])
|
||||
ci_dict = ci_dict and ci_dict[0]
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
||||
@@ -249,7 +465,7 @@ class CIManager(object):
|
||||
|
||||
ci.delete() # TODO: soft delete
|
||||
|
||||
AttributeHistoryManger.add(ci_id, [(None, OperateType.DELETE, None, None)])
|
||||
AttributeHistoryManger.add(None, ci_id, [(None, OperateType.DELETE, ci_dict, None)], ci.type_id)
|
||||
|
||||
ci_delete.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
@@ -260,15 +476,15 @@ class CIManager(object):
|
||||
ci_type = CITypeManager().check_is_existed(ci_type)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr_name=unique_key.name).table
|
||||
value_table = TableMap(attr=unique_key).table
|
||||
|
||||
v = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True) \
|
||||
or abort(404, "not found")
|
||||
or abort(404, ErrFormat.not_found)
|
||||
|
||||
ci = CI.get_by_id(v.ci_id) or abort(404, "CI <{0}> is not found".format(v.ci_id))
|
||||
ci = CI.get_by_id(v.ci_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(v.ci_id)))
|
||||
|
||||
ci.update(heartbeat=datetime.datetime.now())
|
||||
|
||||
@@ -315,25 +531,38 @@ class CIManager(object):
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def _get_cis_from_cache(ci_ids, ret_key=RetKey.NAME, fields=None):
|
||||
def _get_cis_from_cache(ci_ids, ret_key=RetKey.NAME, fields=None, unique_required=False, excludes=None):
|
||||
res = rd.get(ci_ids, REDIS_PREFIX_CI)
|
||||
if res is not None and None not in res and ret_key == RetKey.NAME:
|
||||
res = list(map(json.loads, res))
|
||||
if not fields:
|
||||
if not fields and not excludes:
|
||||
return res
|
||||
else:
|
||||
elif fields:
|
||||
_res = []
|
||||
for d in res:
|
||||
_d = dict()
|
||||
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
||||
_d["ci_type"] = d.get("ci_type")
|
||||
for field in fields:
|
||||
if unique_required:
|
||||
_d[d.get('unique')] = d.get(d.get('unique'))
|
||||
|
||||
for field in fields + ['ci_type_alias', 'unique', 'unique_alias']:
|
||||
_d[field] = d.get(field)
|
||||
_res.append(_d)
|
||||
return _res
|
||||
else:
|
||||
excludes = set(excludes)
|
||||
for i in res:
|
||||
for e in excludes:
|
||||
i.pop(e, None)
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _get_cis_from_db(ci_ids, ret_key=RetKey.NAME, fields=None, value_tables=None):
|
||||
def _get_cis_from_db(ci_ids, ret_key=RetKey.NAME, fields=None, value_tables=None, excludes=None):
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_IDS
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||
|
||||
if not fields:
|
||||
filter_fields_sql = ""
|
||||
else:
|
||||
@@ -344,7 +573,7 @@ class CIManager(object):
|
||||
_fields.append(str(attr.id))
|
||||
filter_fields_sql = "WHERE A.attr_id in ({0})".format(",".join(_fields))
|
||||
|
||||
ci_ids = ",".join(ci_ids)
|
||||
ci_ids = ",".join(map(str, ci_ids))
|
||||
if value_tables is None:
|
||||
value_tables = ValueTypeMap.table_name.values()
|
||||
|
||||
@@ -356,14 +585,23 @@ class CIManager(object):
|
||||
ci_set = set()
|
||||
res = list()
|
||||
ci_dict = dict()
|
||||
unique_id2obj = dict()
|
||||
excludes = excludes and set(excludes)
|
||||
for ci_id, type_id, attr_id, attr_name, attr_alias, value, value_type, is_list in cis:
|
||||
if not fields and excludes and (attr_name in excludes or attr_alias in excludes):
|
||||
continue
|
||||
|
||||
if ci_id not in ci_set:
|
||||
ci_dict = dict()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
ci_dict["ci_id"] = ci_id
|
||||
ci_dict["ci_type"] = type_id
|
||||
ci_dict["_id"] = ci_id
|
||||
ci_dict["_type"] = type_id
|
||||
ci_dict["ci_type"] = ci_type.name
|
||||
ci_dict["ci_type_alias"] = ci_type.alias
|
||||
if ci_type.unique_id not in unique_id2obj:
|
||||
unique_id2obj[ci_type.unique_id] = AttributeCache.get(ci_type.unique_id)
|
||||
ci_dict["unique"] = unique_id2obj[ci_type.unique_id] and unique_id2obj[ci_type.unique_id].name
|
||||
ci_dict["unique_alias"] = unique_id2obj[ci_type.unique_id] and unique_id2obj[ci_type.unique_id].alias
|
||||
ci_set.add(ci_id)
|
||||
res.append(ci_dict)
|
||||
|
||||
@@ -374,7 +612,7 @@ class CIManager(object):
|
||||
elif ret_key == RetKey.ID:
|
||||
attr_key = attr_id
|
||||
else:
|
||||
return abort(400, "invalid ret key")
|
||||
return abort(400, ErrFormat.argument_invalid.format("ret_key"))
|
||||
|
||||
value = ValueTypeMap.serialize2[value_type](value)
|
||||
if is_list:
|
||||
@@ -385,14 +623,17 @@ class CIManager(object):
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_cis_by_ids(cls, ci_ids, ret_key=RetKey.NAME, fields=None, value_tables=None):
|
||||
def get_cis_by_ids(cls, ci_ids, ret_key=RetKey.NAME,
|
||||
fields=None, value_tables=None, unique_required=False, excludes=None):
|
||||
"""
|
||||
|
||||
:param ci_ids: list of CI instance ID, eg. ['1', '2']
|
||||
:param ret_key: name, id or alias
|
||||
:param fields:
|
||||
:param value_tables:
|
||||
:return:
|
||||
:param fields:
|
||||
:param value_tables:
|
||||
:param unique_required:
|
||||
:param excludes: exclude field list
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not ci_ids:
|
||||
@@ -401,12 +642,12 @@ class CIManager(object):
|
||||
fields = [] if fields is None or not isinstance(fields, list) else fields
|
||||
|
||||
ci_id_tuple = tuple(map(int, ci_ids))
|
||||
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields)
|
||||
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields, unique_required, excludes=excludes)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
current_app.logger.warning("cache not hit...............")
|
||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables)
|
||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables, excludes=excludes)
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
@@ -493,7 +734,29 @@ class CIRelationManager(object):
|
||||
return numfound, len(first_ci_ids), result
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, many_to_one=False):
|
||||
def get_ancestor_ids(cls, ci_ids, level=1):
|
||||
for _ in range(level):
|
||||
cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id.in_(ci_ids)).filter(CIRelation.deleted.is_(False))
|
||||
ci_ids = [i.first_ci_id for i in cis]
|
||||
|
||||
return ci_ids
|
||||
|
||||
@staticmethod
|
||||
def _check_constraint(first_ci_id, second_ci_id, type_relation):
|
||||
if type_relation.constraint == ConstraintEnum.Many2Many:
|
||||
return
|
||||
|
||||
first_existed = CIRelation.get_by(first_ci_id=first_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
second_existed = CIRelation.get_by(second_ci_id=second_ci_id, relation_type_id=type_relation.relation_type_id)
|
||||
if type_relation.constraint == ConstraintEnum.One2One and (first_existed or second_existed):
|
||||
return abort(400, ErrFormat.relation_constraint.format("1对1"))
|
||||
|
||||
if type_relation.constraint == ConstraintEnum.One2Many and second_existed:
|
||||
return abort(400, ErrFormat.relation_constraint.format("1对多"))
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
|
||||
first_ci = CIManager.confirm_ci_existed(first_ci_id)
|
||||
second_ci = CIManager.confirm_ci_existed(second_ci_id)
|
||||
@@ -514,18 +777,22 @@ class CIRelationManager(object):
|
||||
first=True,
|
||||
to_dict=False)
|
||||
relation_type_id = type_relation and type_relation.relation_type_id
|
||||
relation_type_id or abort(404, "Relation {0} <-> {1} is not found".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name))
|
||||
relation_type_id or abort(404, ErrFormat.relation_not_found.format("{} -> {}".format(
|
||||
first_ci.ci_type.name, second_ci.ci_type.name)))
|
||||
|
||||
if many_to_one:
|
||||
for item in CIRelation.get_by(second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id,
|
||||
to_dict=False):
|
||||
item.soft_delete()
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(item, operate_type=OperateType.DELETE)
|
||||
if current_app.config.get('USE_ACL'):
|
||||
resource_name = CITypeRelationManager.acl_resource_name(first_ci.ci_type.name,
|
||||
second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION,
|
||||
PermEnum.ADD):
|
||||
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.ADD))
|
||||
|
||||
ci_relation_delete.apply_async(args=(item.first_ci_id, item.second_ci_id), queue=CMDB_QUEUE)
|
||||
else:
|
||||
type_relation = CITypeRelation.get_by_id(relation_type_id)
|
||||
|
||||
cls._check_constraint(first_ci_id, second_ci_id, type_relation)
|
||||
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
@@ -542,7 +809,16 @@ class CIRelationManager(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(cr_id):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, "CIRelation <{0}> is not existed".format(cr_id))
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
|
||||
if not ACLManager().has_permission(
|
||||
resource_name,
|
||||
ResourceTypeEnum.CI_TYPE_RELATION,
|
||||
PermEnum.DELETE):
|
||||
return abort(403, ErrFormat.no_permission.format(resource_name, PermEnum.DELETE))
|
||||
|
||||
cr.delete()
|
||||
|
||||
his_manager = CIRelationHistoryManager()
|
||||
@@ -564,23 +840,34 @@ class CIRelationManager(object):
|
||||
return cls.delete(cr.id)
|
||||
|
||||
@classmethod
|
||||
def batch_update(cls, ci_ids, parents):
|
||||
def batch_update(cls, ci_ids, parents, children):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:return:
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:param children:
|
||||
:return:
|
||||
"""
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
if parents is not None and isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(parent_id, ci_id)
|
||||
|
||||
if parents is not None and isinstance(parents, dict):
|
||||
for attr_name in parents:
|
||||
if parents[attr_name]:
|
||||
attr = AttributeCache.get(attr_name)
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
parent = value_table.get_by(attr_id=attr.id, value=parents[attr_name], first=True, to_dict=False)
|
||||
if not parent:
|
||||
return abort(404, "{0}: {1} is not found".format(attr_name, parents[attr_name]))
|
||||
parent_id = parent.ci_id
|
||||
for ci_id in ci_ids:
|
||||
cls.add(parent_id, ci_id, many_to_one=True)
|
||||
if children is not None and isinstance(children, list):
|
||||
for child_id in children:
|
||||
for ci_id in ci_ids:
|
||||
cls.add(ci_id, child_id)
|
||||
|
||||
@classmethod
|
||||
def batch_delete(cls, ci_ids, parents):
|
||||
"""
|
||||
only for many to one
|
||||
:param ci_ids:
|
||||
:param parents:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if parents is not None and isinstance(parents, list):
|
||||
for parent_id in parents:
|
||||
for ci_id in ci_ids:
|
||||
cls.delete_2(parent_id, ci_id)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,12 @@ class ValueTypeEnum(BaseEnum):
|
||||
JSON = "6"
|
||||
|
||||
|
||||
class ConstraintEnum(BaseEnum):
|
||||
One2Many = "0"
|
||||
One2One = "1"
|
||||
Many2Many = "2"
|
||||
|
||||
|
||||
class CIStatusEnum(BaseEnum):
|
||||
REVIEW = "0"
|
||||
VALIDATE = "1"
|
||||
@@ -32,6 +38,23 @@ class OperateType(BaseEnum):
|
||||
UPDATE = "2"
|
||||
|
||||
|
||||
class CITypeOperateType(BaseEnum):
|
||||
ADD = "0" # 新增模型
|
||||
UPDATE = "1" # 修改模型
|
||||
DELETE = "2" # 删除模型
|
||||
ADD_ATTRIBUTE = "3" # 新增属性
|
||||
UPDATE_ATTRIBUTE = "4" # 修改属性
|
||||
DELETE_ATTRIBUTE = "5" # 删除属性
|
||||
ADD_TRIGGER = "6" # 新增触发器
|
||||
UPDATE_TRIGGER = "7" # 修改触发器
|
||||
DELETE_TRIGGER = "8" # 删除触发器
|
||||
ADD_UNIQUE_CONSTRAINT = "9" # 新增联合唯一
|
||||
UPDATE_UNIQUE_CONSTRAINT = "10" # 修改联合唯一
|
||||
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一
|
||||
ADD_RELATION = "12" # 新增关系
|
||||
DELETE_RELATION = "13" # 删除关系
|
||||
|
||||
|
||||
class RetKey(BaseEnum):
|
||||
ID = "id"
|
||||
NAME = "name"
|
||||
@@ -40,21 +63,41 @@ class RetKey(BaseEnum):
|
||||
|
||||
class ResourceTypeEnum(BaseEnum):
|
||||
CI = "CIType"
|
||||
RELATION_VIEW = "RelationView"
|
||||
CI_TYPE = "CIType" # create/update/delete/read/config/grant
|
||||
CI_TYPE_RELATION = "CITypeRelation" # create/delete/grant
|
||||
RELATION_VIEW = "RelationView" # read/update/delete/grant
|
||||
CI_FILTER = "CIFilter" # read
|
||||
|
||||
|
||||
class PermEnum(BaseEnum):
|
||||
ADD = "add"
|
||||
ADD = "create"
|
||||
UPDATE = "update"
|
||||
DELETE = "delete"
|
||||
READ = "read"
|
||||
CONFIG = "config"
|
||||
GRANT = "grant"
|
||||
|
||||
|
||||
class RoleEnum(BaseEnum):
|
||||
CONFIG = "admin"
|
||||
CONFIG = "cmdb_admin"
|
||||
CMDB_READ_ALL = "CMDB_READ_ALL"
|
||||
|
||||
|
||||
CMDB_QUEUE = "cmdb_async"
|
||||
REDIS_PREFIX_CI = "CMDB_CI"
|
||||
class AutoDiscoveryType(BaseEnum):
|
||||
AGENT = "agent"
|
||||
SNMP = "snmp"
|
||||
HTTP = "http"
|
||||
|
||||
|
||||
class AttributeDefaultValueEnum(BaseEnum):
|
||||
CREATED_AT = "$created_at"
|
||||
UPDATED_AT = "$updated_at"
|
||||
AUTO_INC_ID = "$auto_inc_id"
|
||||
|
||||
|
||||
CMDB_QUEUE = "one_cmdb_async"
|
||||
REDIS_PREFIX_CI = "ONE_CMDB"
|
||||
REDIS_PREFIX_CI_RELATION = "CMDB_CI_RELATION"
|
||||
|
||||
L_TYPE = None
|
||||
L_CI = None
|
||||
|
74
cmdb-api/api/lib/cmdb/custom_dashboard.py
Normal file
74
cmdb-api/api/lib/cmdb/custom_dashboard.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.models.cmdb import CustomDashboard
|
||||
from api.models.cmdb import SystemConfig
|
||||
|
||||
|
||||
class CustomDashboardManager(object):
|
||||
cls = CustomDashboard
|
||||
|
||||
@staticmethod
|
||||
def get():
|
||||
return sorted(CustomDashboard.get_by(to_dict=True), key=lambda x: (x["category"], x['order']))
|
||||
|
||||
@staticmethod
|
||||
def add(**kwargs):
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
if kwargs.get('name'):
|
||||
CustomDashboard.get_by(name=kwargs['name']) and abort(400, ErrFormat.custom_name_duplicate)
|
||||
|
||||
new = CustomDashboard.create(**kwargs)
|
||||
|
||||
CMDBCounterCache.update(new.to_dict())
|
||||
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def update(_id, **kwargs):
|
||||
from api.lib.cmdb.cache import CMDBCounterCache
|
||||
|
||||
existed = CustomDashboard.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
new = existed.update(**kwargs)
|
||||
|
||||
CMDBCounterCache.update(new.to_dict())
|
||||
|
||||
return new
|
||||
|
||||
@staticmethod
|
||||
def batch_update(id2options):
|
||||
for _id in id2options:
|
||||
existed = CustomDashboard.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
existed.update(options=id2options[_id])
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
existed = CustomDashboard.get_by_id(_id) or abort(404, ErrFormat.not_found)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
|
||||
class SystemConfigManager(object):
|
||||
cls = SystemConfig
|
||||
|
||||
@staticmethod
|
||||
def get(name):
|
||||
return SystemConfig.get_by(name=name, first=True, to_dict=True)
|
||||
|
||||
@staticmethod
|
||||
def create_or_update(name, option):
|
||||
existed = SystemConfig.get_by(name=name, first=True, to_dict=False)
|
||||
if existed is not None:
|
||||
return existed.update(option=option)
|
||||
else:
|
||||
return SystemConfig.create(name=name, option=option)
|
||||
|
||||
@staticmethod
|
||||
def delete(name):
|
||||
existed = SystemConfig.get_by(name=name, first=True, to_dict=False) or abort(404, ErrFormat.not_found)
|
||||
|
||||
existed.soft_delete()
|
@@ -10,76 +10,174 @@ from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import RelationTypeCache
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import CIRelationHistory
|
||||
from api.models.cmdb import CITypeHistory
|
||||
from api.models.cmdb import CITypeTrigger
|
||||
from api.models.cmdb import CITypeUniqueConstraint
|
||||
from api.models.cmdb import OperationRecord
|
||||
|
||||
|
||||
class AttributeHistoryManger(object):
|
||||
@staticmethod
|
||||
def get_records(start, end, username, page, page_size):
|
||||
records = db.session.query(OperationRecord).filter(OperationRecord.deleted.is_(False))
|
||||
numfound = db.session.query(db.func.count(OperationRecord.id)).filter(OperationRecord.deleted.is_(False))
|
||||
def get_records_for_attributes(start, end, username, page, page_size, operate_type, type_id,
|
||||
ci_id=None, attr_id=None):
|
||||
|
||||
records = db.session.query(OperationRecord, AttributeHistory).join(
|
||||
AttributeHistory, OperationRecord.id == AttributeHistory.record_id)
|
||||
if start:
|
||||
records = records.filter(OperationRecord.created_at >= start)
|
||||
numfound = numfound.filter(OperationRecord.created_at >= start)
|
||||
if end:
|
||||
records = records.filter(OperationRecord.created_at <= end)
|
||||
numfound = records.filter(OperationRecord.created_at <= end)
|
||||
if type_id:
|
||||
records = records.filter(OperationRecord.type_id == type_id)
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user:
|
||||
records = records.filter(OperationRecord.uid == user.uid)
|
||||
else:
|
||||
return abort(404, "User <{0}> is not found".format(username))
|
||||
return abort(404, ErrFormat.user_not_found.format(username))
|
||||
if operate_type:
|
||||
records = records.filter(AttributeHistory.operate_type == operate_type)
|
||||
|
||||
records = records.order_by(-OperationRecord.id).offset(page_size * (page - 1)).limit(page_size).all()
|
||||
if ci_id is not None:
|
||||
records = records.filter(AttributeHistory.ci_id == ci_id)
|
||||
|
||||
if attr_id is not None:
|
||||
records = records.filter(AttributeHistory.attr_id == attr_id)
|
||||
|
||||
records = records.order_by(AttributeHistory.id.desc()).offset(page_size * (page - 1)).limit(page_size).all()
|
||||
total = len(records)
|
||||
numfound = numfound.first()[0]
|
||||
res = []
|
||||
|
||||
res = {}
|
||||
for record in records:
|
||||
_res = record.to_dict()
|
||||
_res["user"] = UserCache.get(_res.get("uid")).nickname or UserCache.get(_res.get("uid")).username
|
||||
record_id = record.OperationRecord.id
|
||||
attr_hist = record.AttributeHistory.to_dict()
|
||||
attr_hist['attr'] = AttributeCache.get(attr_hist['attr_id'])
|
||||
if attr_hist['attr']:
|
||||
attr_hist['attr_name'] = attr_hist['attr'].name
|
||||
attr_hist['attr_alias'] = attr_hist['attr'].alias
|
||||
attr_hist.pop("attr")
|
||||
|
||||
attr_history = AttributeHistory.get_by(record_id=_res.get("id"), to_dict=False)
|
||||
_res["attr_history"] = [AttributeCache.get(h.attr_id).attr_alias for h in attr_history]
|
||||
if record_id not in res:
|
||||
record_dict = record.OperationRecord.to_dict()
|
||||
record_dict["user"] = UserCache.get(record_dict.get("uid"))
|
||||
if record_dict["user"]:
|
||||
record_dict['user'] = record_dict['user'].nickname
|
||||
|
||||
rel_history = CIRelationHistory.get_by(record_id=_res.get("id"), to_dict=False)
|
||||
rel_statis = {}
|
||||
for rel in rel_history:
|
||||
if rel.operate_type not in rel_statis:
|
||||
rel_statis[rel.operate_type] = 1
|
||||
else:
|
||||
rel_statis[rel.operate_type] += 1
|
||||
_res["rel_history"] = rel_statis
|
||||
res.append(_res)
|
||||
res[record_id] = [record_dict, [attr_hist]]
|
||||
else:
|
||||
res[record_id][1].append(attr_hist)
|
||||
|
||||
return numfound, total, res
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
|
||||
if attr_filter:
|
||||
res = [i for i in res if i.get('attr_name') in attr_filter]
|
||||
|
||||
res = [res[i] for i in sorted(res.keys(), reverse=True)]
|
||||
|
||||
return total, res
|
||||
|
||||
@staticmethod
|
||||
def get_records_for_relation(start, end, username, page, page_size, operate_type, type_id,
|
||||
first_ci_id=None, second_ci_id=None):
|
||||
|
||||
records = db.session.query(OperationRecord, CIRelationHistory).join(
|
||||
CIRelationHistory, OperationRecord.id == CIRelationHistory.record_id)
|
||||
if start:
|
||||
records = records.filter(OperationRecord.created_at >= start)
|
||||
if end:
|
||||
records = records.filter(OperationRecord.created_at <= end)
|
||||
if type_id:
|
||||
records = records.filter(OperationRecord.type_id == type_id)
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user:
|
||||
records = records.filter(OperationRecord.uid == user.uid)
|
||||
else:
|
||||
return abort(404, ErrFormat.user_not_found.format(username))
|
||||
if operate_type:
|
||||
records = records.filter(CIRelationHistory.operate_type == operate_type)
|
||||
|
||||
if first_ci_id is not None:
|
||||
records = records.filter(CIRelationHistory.first_ci_id == first_ci_id)
|
||||
|
||||
if second_ci_id is not None:
|
||||
records = records.filter(CIRelationHistory.second_ci_id == second_ci_id)
|
||||
|
||||
records = records.order_by(CIRelationHistory.id.desc()).offset(page_size * (page - 1)).limit(page_size).all()
|
||||
total = len(records)
|
||||
|
||||
res = {}
|
||||
ci_ids = set()
|
||||
for record in records:
|
||||
record_id = record.OperationRecord.id
|
||||
rel_hist = record.CIRelationHistory.to_dict()
|
||||
|
||||
ci_ids.add(rel_hist['first_ci_id'])
|
||||
ci_ids.add(rel_hist['second_ci_id'])
|
||||
if record_id not in res:
|
||||
record_dict = record.OperationRecord.to_dict()
|
||||
record_dict["user"] = UserCache.get(record_dict.get("uid"))
|
||||
if record_dict["user"]:
|
||||
record_dict['user'] = record_dict['user'].nickname
|
||||
|
||||
res[record_id] = [record_dict, [rel_hist]]
|
||||
else:
|
||||
res[record_id][1].append(rel_hist)
|
||||
|
||||
res = [res[i] for i in sorted(res.keys(), reverse=True)]
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
cis = CIManager().get_cis_by_ids(list(ci_ids),
|
||||
unique_required=True)
|
||||
cis = {i['_id']: i for i in cis}
|
||||
|
||||
return total, res, cis
|
||||
|
||||
@staticmethod
|
||||
def get_by_ci_id(ci_id):
|
||||
res = db.session.query(AttributeHistory, Attribute, OperationRecord).join(
|
||||
Attribute, Attribute.id == AttributeHistory.attr_id).join(
|
||||
OperationRecord, OperationRecord.id == AttributeHistory.record_id).filter(
|
||||
AttributeHistory.ci_id == ci_id).order_by(OperationRecord.id.desc())
|
||||
return [dict(attr_name=i.Attribute.name,
|
||||
attr_alias=i.Attribute.alias,
|
||||
operate_type=i.AttributeHistory.operate_type,
|
||||
username=UserCache.get(i.OperationRecord.uid).nickname,
|
||||
old=i.AttributeHistory.old,
|
||||
new=i.AttributeHistory.new,
|
||||
created_at=i.OperationRecord.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=i.OperationRecord.id,
|
||||
hid=i.AttributeHistory.id
|
||||
) for i in res]
|
||||
AttributeHistory.ci_id == ci_id).order_by(AttributeHistory.id.desc())
|
||||
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
ci = CIManager.get_by_id(ci_id)
|
||||
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(ci.type_id) if ci else None
|
||||
result = []
|
||||
for i in res:
|
||||
attr = i.Attribute
|
||||
if attr_filter and attr.name not in attr_filter:
|
||||
continue
|
||||
|
||||
user = UserCache.get(i.OperationRecord.uid)
|
||||
hist = i.AttributeHistory
|
||||
record = i.OperationRecord
|
||||
item = dict(attr_name=attr.name,
|
||||
attr_alias=attr.alias,
|
||||
operate_type=hist.operate_type,
|
||||
username=user and user.nickname,
|
||||
old=hist.old,
|
||||
new=hist.new,
|
||||
created_at=record.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=record.id,
|
||||
hid=hist.id
|
||||
)
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_record_detail(record_id):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
record = OperationRecord.get_by_id(record_id) or abort(404, "Record <{0}> is not found".format(record_id))
|
||||
record = OperationRecord.get_by_id(record_id) or \
|
||||
abort(404, ErrFormat.record_not_found.format("id={}".format(record_id)))
|
||||
|
||||
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
||||
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -101,8 +199,10 @@ class AttributeHistoryManger(object):
|
||||
return username, timestamp, attr_dict, rel_dict
|
||||
|
||||
@staticmethod
|
||||
def add(ci_id, history_list):
|
||||
record = OperationRecord.create(uid=g.user.uid)
|
||||
def add(record_id, ci_id, history_list, type_id=None, flush=False, commit=True):
|
||||
if record_id is None:
|
||||
record = OperationRecord.create(uid=g.user.uid, type_id=type_id)
|
||||
record_id = record.id
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
AttributeHistory.create(attr_id=attr_id,
|
||||
@@ -110,7 +210,11 @@ class AttributeHistoryManger(object):
|
||||
old=json.dumps(old) if isinstance(old, (dict, list)) else old,
|
||||
new=json.dumps(new) if isinstance(new, (dict, list)) else new,
|
||||
ci_id=ci_id,
|
||||
record_id=record.id)
|
||||
record_id=record_id,
|
||||
flush=flush,
|
||||
commit=commit)
|
||||
|
||||
return record_id
|
||||
|
||||
|
||||
class CIRelationHistoryManager(object):
|
||||
@@ -124,3 +228,61 @@ class CIRelationHistoryManager(object):
|
||||
first_ci_id=rel_obj.first_ci_id,
|
||||
second_ci_id=rel_obj.second_ci_id,
|
||||
relation_type_id=rel_obj.relation_type_id)
|
||||
|
||||
|
||||
class CITypeHistoryManager(object):
|
||||
@staticmethod
|
||||
def get(page, page_size, username=None, type_id=None, operate_type=None):
|
||||
query = CITypeHistory.get_by(only_query=True)
|
||||
if type_id is not None:
|
||||
query = query.filter(CITypeHistory.type_id == type_id)
|
||||
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user:
|
||||
query = query.filter(CITypeHistory.uid == user.uid)
|
||||
else:
|
||||
return abort(404, ErrFormat.user_not_found.format(username))
|
||||
|
||||
if operate_type is not None:
|
||||
query = query.filter(CITypeHistory.operate_type == operate_type)
|
||||
|
||||
numfound = query.count()
|
||||
|
||||
query = query.order_by(CITypeHistory.id.desc())
|
||||
result = query.offset((page - 1) * page_size).limit(page_size)
|
||||
result = [i.to_dict() for i in result]
|
||||
for res in result:
|
||||
res["user"] = UserCache.get(res.get("uid"))
|
||||
if res["user"]:
|
||||
res['user'] = res['user'].nickname
|
||||
if res.get('attr_id'):
|
||||
attr = AttributeCache.get(res['attr_id'])
|
||||
res['attr'] = attr and attr.to_dict()
|
||||
elif res.get('trigger_id'):
|
||||
trigger = CITypeTrigger.get_by_id(res['trigger_id'])
|
||||
res['trigger'] = trigger and trigger.to_dict()
|
||||
elif res.get('unique_constraint_id'):
|
||||
unique_constraint = CITypeUniqueConstraint.get_by_id(res['unique_constraint_id'])
|
||||
res['unique_constraint'] = unique_constraint and unique_constraint.to_dict()
|
||||
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def add(operate_type, type_id, attr_id=None, trigger_id=None, unique_constraint_id=None, change=None):
|
||||
if type_id is None and attr_id is not None:
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
type_ids = [i.type_id for i in CITypeAttribute.get_by(attr_id=attr_id, to_dict=False)]
|
||||
else:
|
||||
type_ids = [type_id]
|
||||
|
||||
for _type_id in type_ids:
|
||||
payload = dict(operate_type=operate_type,
|
||||
type_id=_type_id,
|
||||
uid=g.user.uid,
|
||||
attr_id=attr_id,
|
||||
trigger_id=trigger_id,
|
||||
unique_constraint_id=unique_constraint_id,
|
||||
change=change)
|
||||
|
||||
CITypeHistory.create(**payload)
|
||||
|
177
cmdb-api/api/lib/cmdb/perms.py
Normal file
177
cmdb-api/api/lib/cmdb/perms.py
Normal file
@@ -0,0 +1,177 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import functools
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.mixin import DBMixin
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.perm.acl.acl import validate_permission
|
||||
from api.models.cmdb import CIFilterPerms
|
||||
|
||||
|
||||
class CIFilterPermsCRUD(DBMixin):
|
||||
cls = CIFilterPerms
|
||||
|
||||
def get(self, type_id):
|
||||
res = self.cls.get_by(type_id=type_id, to_dict=True)
|
||||
result = {}
|
||||
for i in res:
|
||||
if i['attr_filter']:
|
||||
i['attr_filter'] = i['attr_filter'].split(',')
|
||||
|
||||
if i['rid'] not in result:
|
||||
result[i['rid']] = i
|
||||
else:
|
||||
if i['attr_filter']:
|
||||
if not result[i['rid']]['attr_filter']:
|
||||
result[i['rid']]['attr_filter'] = []
|
||||
|
||||
result[i['rid']]['attr_filter'] += i['attr_filter']
|
||||
result[i['rid']]['attr_filter'] = list(set(i['attr_filter']))
|
||||
if i['ci_filter']:
|
||||
if not result[i['rid']]['ci_filter']:
|
||||
result[i['rid']]['ci_filter'] = ""
|
||||
result[i['rid']]['ci_filter'] += (i['ci_filter'] or "")
|
||||
|
||||
return result
|
||||
|
||||
def get_by_ids(self, _ids, type_id=None):
|
||||
if not _ids:
|
||||
return {}
|
||||
|
||||
if type_id is not None:
|
||||
res = self.cls.get_by(type_id=type_id, __func_in___key_id=_ids, to_dict=True)
|
||||
else:
|
||||
res = self.cls.get_by(__func_in___key_id=_ids, to_dict=True)
|
||||
|
||||
result = {}
|
||||
for i in res:
|
||||
if i['attr_filter']:
|
||||
i['attr_filter'] = i['attr_filter'].split(',')
|
||||
|
||||
if i['type_id'] not in result:
|
||||
result[i['type_id']] = i
|
||||
else:
|
||||
if i['attr_filter']:
|
||||
if not result[i['type_id']]['attr_filter']:
|
||||
result[i['type_id']]['attr_filter'] = []
|
||||
|
||||
result[i['type_id']]['attr_filter'] += i['attr_filter']
|
||||
result[i['type_id']]['attr_filter'] = list(set(i['attr_filter']))
|
||||
if i['ci_filter']:
|
||||
if not result[i['type_id']]['ci_filter']:
|
||||
result[i['type_id']]['ci_filter'] = ""
|
||||
result[i['type_id']]['ci_filter'] += (i['ci_filter'] or "")
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_attr_filter(cls, type_id):
|
||||
if is_app_admin('cmdb') or g.user.username in ('worker', 'cmdb_agent'):
|
||||
return []
|
||||
|
||||
res2 = ACLManager('cmdb').get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
type2filter_perms = cls().get_by_ids(list(map(int, [i['name'] for i in res2])), type_id=type_id)
|
||||
return type2filter_perms.get(type_id, {}).get('attr_filter') or []
|
||||
|
||||
def _can_add(self, **kwargs):
|
||||
ci_filter = kwargs.get('ci_filter')
|
||||
attr_filter = kwargs.get('attr_filter') or ""
|
||||
|
||||
if 'attr_filter' in kwargs:
|
||||
kwargs['attr_filter'] = kwargs['attr_filter'] or None
|
||||
|
||||
if attr_filter:
|
||||
kwargs['attr_filter'] = ','.join(attr_filter or [])
|
||||
|
||||
if ci_filter and not kwargs.get('name'):
|
||||
return abort(400, ErrFormat.ci_filter_name_cannot_be_empty)
|
||||
|
||||
if ci_filter and ci_filter.startswith('q='):
|
||||
kwargs['ci_filter'] = kwargs['ci_filter'][2:]
|
||||
|
||||
return kwargs
|
||||
|
||||
def add(self, **kwargs):
|
||||
kwargs = self._can_add(**kwargs) or kwargs
|
||||
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
first=True, to_dict=False)
|
||||
if obj is not None:
|
||||
obj = obj.update(filter_none=False, **kwargs)
|
||||
if not obj.attr_filter and not obj.ci_filter:
|
||||
if current_app.config.get('USE_ACL'):
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
else:
|
||||
if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'):
|
||||
return
|
||||
|
||||
obj = self.cls.create(**kwargs)
|
||||
|
||||
if current_app.config.get('USE_ACL'):
|
||||
try:
|
||||
ACLManager().add_resource(obj.id, ResourceTypeEnum.CI_FILTER)
|
||||
except:
|
||||
pass
|
||||
ACLManager().grant_resource_to_role_by_rid(obj.id,
|
||||
kwargs.get('rid'),
|
||||
ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
return obj
|
||||
|
||||
def _can_update(self, **kwargs):
|
||||
pass
|
||||
|
||||
def _can_delete(self, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self, **kwargs):
|
||||
obj = self.cls.get_by(type_id=kwargs.get('type_id'),
|
||||
rid=kwargs.get('rid'),
|
||||
first=True, to_dict=False)
|
||||
|
||||
if obj is not None:
|
||||
if current_app.config.get('USE_ACL'):
|
||||
ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
|
||||
|
||||
obj.soft_delete()
|
||||
|
||||
|
||||
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
|
||||
def decorator_has_perm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_has_perm(*args, **kwargs):
|
||||
if not arg_name:
|
||||
return
|
||||
resource = request.view_args.get(arg_name) or request.values.get(arg_name)
|
||||
if callback is not None and resource:
|
||||
resource = callback(resource)
|
||||
|
||||
if current_app.config.get("USE_ACL") and resource:
|
||||
if g.user.username == "worker" or g.user.username == "cmdb_agent":
|
||||
request.values['__is_admin'] = True
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if is_app_admin(app):
|
||||
request.values['__is_admin'] = True
|
||||
return func(*args, **kwargs)
|
||||
|
||||
validate_permission(resource.name, resource_type, perm, app)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_has_perm
|
||||
|
||||
return decorator_has_perm
|
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
import six
|
||||
import toposort
|
||||
@@ -15,17 +14,25 @@ from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributesCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum
|
||||
from api.lib.cmdb.const import PermEnum, ResourceTypeEnum, RoleEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.exception import AbortException
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import PreferenceSearchOption
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
|
||||
|
||||
class PreferenceManager(object):
|
||||
pref_attr_cls = PreferenceShowAttributes
|
||||
pref_tree_cls = PreferenceTreeView
|
||||
pref_rel_cls = PreferenceRelationView
|
||||
pre_so_cls = PreferenceSearchOption
|
||||
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
@@ -36,19 +43,65 @@ class PreferenceManager(object):
|
||||
type_ids = list(set([i.type_id for i in types + tree_types]))
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@staticmethod
|
||||
def get_types2(instance=False, tree=False):
|
||||
"""
|
||||
{
|
||||
self: {instance: [], tree: [], type_id2subs_time: {type_id: subs_time}},
|
||||
type_id2users: {type_id: []}
|
||||
}
|
||||
:param instance:
|
||||
:param tree:
|
||||
:return:
|
||||
"""
|
||||
result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()),
|
||||
type_id2users=dict())
|
||||
if instance:
|
||||
types = db.session.query(PreferenceShowAttributes.type_id,
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(
|
||||
PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id)
|
||||
for i in types:
|
||||
if i.uid == g.user.uid:
|
||||
result['self']['instance'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, []).append(i.uid)
|
||||
|
||||
if tree:
|
||||
types = PreferenceTreeView.get_by(to_dict=False)
|
||||
for i in types:
|
||||
if i.uid == g.user.uid:
|
||||
result['self']['tree'].append(i.type_id)
|
||||
if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")):
|
||||
result['self']['type_id2subs_time'][i.type_id] = i.created_at
|
||||
|
||||
result['type_id2users'].setdefault(i.type_id, [])
|
||||
if i.uid not in result['type_id2users'][i.type_id]:
|
||||
result['type_id2users'][i.type_id].append(i.uid)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_show_attributes(type_id):
|
||||
if not isinstance(type_id, six.integer_types):
|
||||
type_id = CITypeCache.get(type_id).id
|
||||
_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 == g.user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).order_by(
|
||||
CITypeAttribute.order).all()
|
||||
result = [i.PreferenceShowAttributes.attr.to_dict() for i in attrs]
|
||||
CITypeAttribute.type_id == type_id).all()
|
||||
|
||||
result = []
|
||||
for i in sorted(attrs, key=lambda x: x.PreferenceShowAttributes.order):
|
||||
item = i.PreferenceShowAttributes.attr.to_dict()
|
||||
item.update(dict(is_fixed=i.PreferenceShowAttributes.is_fixed))
|
||||
result.append(item)
|
||||
|
||||
is_subscribed = True
|
||||
if not attrs:
|
||||
attrs = db.session.query(CITypeAttribute).filter(
|
||||
@@ -60,15 +113,20 @@ class PreferenceManager(object):
|
||||
|
||||
for i in result:
|
||||
if i["is_choice"]:
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(i["id"], i["value_type"])))
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(
|
||||
i["id"], i["value_type"], i["choice_web_hook"])))
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
@classmethod
|
||||
def create_or_update_show_attributes(cls, type_id, attr_order):
|
||||
existed_all = PreferenceShowAttributes.get_by(type_id=type_id, uid=g.user.uid, to_dict=False)
|
||||
for _attr, order in attr_order:
|
||||
attr = AttributeCache.get(_attr) or abort(404, "Attribute <{0}> does not exist".format(_attr))
|
||||
for x, order in attr_order:
|
||||
if isinstance(x, list):
|
||||
_attr, is_fixed = x
|
||||
else:
|
||||
_attr, is_fixed = x, False
|
||||
attr = AttributeCache.get(_attr) or abort(404, ErrFormat.attribute_not_found.format("id={}".format(_attr)))
|
||||
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
attr_id=attr.id,
|
||||
@@ -78,11 +136,12 @@ class PreferenceManager(object):
|
||||
PreferenceShowAttributes.create(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
attr_id=attr.id,
|
||||
order=order)
|
||||
order=order,
|
||||
is_fixed=is_fixed)
|
||||
else:
|
||||
existed.update(order=order)
|
||||
existed.update(order=order, is_fixed=is_fixed)
|
||||
|
||||
attr_dict = {int(i): j for i, j in attr_order}
|
||||
attr_dict = {int(i[0]) if isinstance(i, list) else int(i): j for i, j in attr_order}
|
||||
for i in existed_all:
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
@@ -92,9 +151,19 @@ class PreferenceManager(object):
|
||||
res = PreferenceTreeView.get_by(uid=g.user.uid, to_dict=True)
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
item.update(CITypeCache.get(item['type_id']).to_dict())
|
||||
item.update(dict(levels=[AttributeCache.get(l).to_dict()
|
||||
for l in item["levels"].split(",") if AttributeCache.get(l)]))
|
||||
ci_type = CITypeCache.get(item['type_id']).to_dict()
|
||||
attr_filter = CIFilterPermsCRUD.get_attr_filter(ci_type['id'])
|
||||
ci_type.pop('id', None)
|
||||
ci_type.pop('created_at', None)
|
||||
ci_type.pop('updated_at', None)
|
||||
item.update(ci_type)
|
||||
|
||||
_levels = []
|
||||
for i in item["levels"]:
|
||||
attr = AttributeCache.get(i)
|
||||
if attr and (not attr_filter or attr.name in attr_filter):
|
||||
_levels.append(attr.to_dict())
|
||||
item.update(dict(levels=_levels))
|
||||
|
||||
return res
|
||||
|
||||
@@ -105,8 +174,7 @@ class PreferenceManager(object):
|
||||
for attr in attrs:
|
||||
attr = AttributeCache.get(attr.attr_id)
|
||||
if i == attr.id or i == attr.name or i == attr.alias:
|
||||
levels[idx] = str(attr.id)
|
||||
levels = ",".join(levels)
|
||||
levels[idx] = attr.id
|
||||
|
||||
existed = PreferenceTreeView.get_by(uid=g.user.uid, type_id=type_id, to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
@@ -124,9 +192,9 @@ class PreferenceManager(object):
|
||||
if current_app.config.get("USE_ACL"):
|
||||
for i in _views:
|
||||
try:
|
||||
if ACLManager().has_permission(i.get('name'),
|
||||
ResourceTypeEnum.RELATION_VIEW,
|
||||
PermEnum.READ):
|
||||
if i.get('is_public') or ACLManager().has_permission(i.get('name'),
|
||||
ResourceTypeEnum.RELATION_VIEW,
|
||||
PermEnum.READ):
|
||||
views.append(i)
|
||||
except AbortException:
|
||||
pass
|
||||
@@ -137,7 +205,7 @@ class PreferenceManager(object):
|
||||
result = dict()
|
||||
name2id = list()
|
||||
for view in views:
|
||||
view2cr_ids.setdefault(view['name'], []).extend(json.loads(view['cr_ids']))
|
||||
view2cr_ids.setdefault(view['name'], []).extend(view['cr_ids'])
|
||||
name2id.append([view['name'], view['id']])
|
||||
|
||||
id2type = dict()
|
||||
@@ -179,14 +247,14 @@ class PreferenceManager(object):
|
||||
return result, id2type, sorted(name2id, key=lambda x: x[1])
|
||||
|
||||
@classmethod
|
||||
def create_or_update_relation_view(cls, name, cr_ids):
|
||||
def create_or_update_relation_view(cls, name, cr_ids, is_public=False):
|
||||
if not cr_ids:
|
||||
return abort(400, "Node must be selected")
|
||||
return abort(400, ErrFormat.preference_relation_view_node_required)
|
||||
|
||||
existed = PreferenceRelationView.get_by(name=name, to_dict=False, first=True)
|
||||
current_app.logger.debug(existed)
|
||||
if existed is None:
|
||||
PreferenceRelationView.create(name=name, cr_ids=json.dumps(cr_ids))
|
||||
PreferenceRelationView.create(name=name, cr_ids=cr_ids, uid=g.user.uid, is_public=is_public)
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||
@@ -206,3 +274,65 @@ class PreferenceManager(object):
|
||||
ACLManager().del_resource(name, ResourceTypeEnum.RELATION_VIEW)
|
||||
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def get_search_option(**kwargs):
|
||||
query = PreferenceSearchOption.get_by(only_query=True)
|
||||
query = query.filter(PreferenceSearchOption.uid == g.user.uid)
|
||||
|
||||
for k in kwargs:
|
||||
if hasattr(PreferenceSearchOption, k) and kwargs[k]:
|
||||
query = query.filter(getattr(PreferenceSearchOption, k) == kwargs[k])
|
||||
|
||||
return [i.to_dict() for i in query]
|
||||
|
||||
@staticmethod
|
||||
def add_search_option(**kwargs):
|
||||
kwargs['uid'] = g.user.uid
|
||||
|
||||
existed = PreferenceSearchOption.get_by(uid=g.user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
type_id=kwargs.get('type_id'),
|
||||
)
|
||||
if existed:
|
||||
return abort(400, ErrFormat.preference_search_option_exists)
|
||||
|
||||
return PreferenceSearchOption.create(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def update_search_option(_id, **kwargs):
|
||||
|
||||
existed = PreferenceSearchOption.get_by_id(_id) or abort(404, ErrFormat.preference_search_option_not_found)
|
||||
|
||||
if g.user.uid != existed.uid:
|
||||
return abort(400, ErrFormat.no_permission2)
|
||||
|
||||
other = PreferenceSearchOption.get_by(uid=g.user.uid,
|
||||
name=kwargs.get('name'),
|
||||
prv_id=kwargs.get('prv_id'),
|
||||
ptv_id=kwargs.get('ptv_id'),
|
||||
type_id=kwargs.get('type_id'),
|
||||
)
|
||||
if other.id != _id:
|
||||
return abort(400, ErrFormat.preference_search_option_exists)
|
||||
|
||||
return existed.update(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def delete_search_option(_id):
|
||||
existed = PreferenceSearchOption.get_by_id(_id) or abort(404, ErrFormat.preference_search_option_not_found)
|
||||
|
||||
if g.user.uid != existed.uid:
|
||||
return abort(400, ErrFormat.no_permission2)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def delete_by_type_id(type_id, uid):
|
||||
for i in PreferenceShowAttributes.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
63
cmdb-api/api/lib/cmdb/query_sql.py
Normal file
63
cmdb-api/api/lib/cmdb/query_sql.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
QUERY_CIS_BY_VALUE_TABLE = """
|
||||
SELECT attr.name AS attr_name,
|
||||
attr.alias AS attr_alias,
|
||||
attr.value_type,
|
||||
attr.is_list,
|
||||
c_cis.type_id,
|
||||
{0}.ci_id,
|
||||
{0}.attr_id,
|
||||
{0}.value
|
||||
FROM {0}
|
||||
INNER JOIN c_cis ON {0}.ci_id=c_cis.id
|
||||
AND {0}.`ci_id` IN ({1})
|
||||
INNER JOIN c_attributes as attr ON attr.id = {0}.attr_id
|
||||
"""
|
||||
|
||||
# {2}: value_table
|
||||
QUERY_CIS_BY_IDS = """
|
||||
SELECT A.ci_id,
|
||||
A.type_id,
|
||||
A.attr_id,
|
||||
A.attr_name,
|
||||
A.attr_alias,
|
||||
A.value,
|
||||
A.value_type,
|
||||
A.is_list
|
||||
FROM
|
||||
({1}) AS A {0}
|
||||
ORDER BY A.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY1 = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN c_attributes AS attr ON attr.id={0}.attr_id
|
||||
WHERE attr.name="{1}"
|
||||
GROUP BY {0}.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
||||
WHERE {0}.attr_id={2:d}
|
||||
GROUP BY {0}.value
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_ATTR_NAME = """
|
||||
SELECT {0}.ci_id
|
||||
FROM {0}
|
||||
WHERE {0}.attr_id={1:d}
|
||||
AND {0}.value {2}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
SELECT c_cis.id AS ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.type_id in ({0})
|
||||
"""
|
@@ -3,10 +3,13 @@
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
class RelationTypeManager(object):
|
||||
cls = RelationType
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
return RelationType.get_by(to_dict=False)
|
||||
@@ -21,17 +24,21 @@ class RelationTypeManager(object):
|
||||
|
||||
@staticmethod
|
||||
def add(name):
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and abort(400, "It's already existed")
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and \
|
||||
abort(400, ErrFormat.relation_type_exists.format(name))
|
||||
|
||||
return RelationType.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(rel_id, name):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(404, "RelationType <{0}> does not exist".format(rel_id))
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
return existed.update(name=name)
|
||||
|
||||
@staticmethod
|
||||
def delete(rel_id):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(404, "RelationType <{0}> does not exist".format(rel_id))
|
||||
existed = RelationType.get_by_id(rel_id) or \
|
||||
abort(404, ErrFormat.relation_type_not_found.format("id={}".format(rel_id)))
|
||||
|
||||
existed.soft_delete()
|
||||
|
93
cmdb-api/api/lib/cmdb/resp_format.py
Normal file
93
cmdb-api/api/lib/cmdb/resp_format.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.resp_format import CommonErrFormat
|
||||
|
||||
|
||||
class ErrFormat(CommonErrFormat):
|
||||
invalid_relation_type = "无效的关系类型: {}"
|
||||
ci_type_not_found = "模型不存在!"
|
||||
argument_attributes_must_be_list = "参数 attributes 类型必须是列表"
|
||||
argument_file_not_found = "文件似乎并未上传"
|
||||
|
||||
attribute_not_found = "属性 {} 不存在!"
|
||||
attribute_value_type_cannot_change = "属性的值类型不允许修改!"
|
||||
attribute_list_value_cannot_change = "多值不被允许修改!"
|
||||
attribute_index_cannot_change = "修改索引 非管理员不被允许!"
|
||||
attribute_index_change_failed = "索引切换失败!"
|
||||
invalid_choice_values = "预定义值的类型不对!"
|
||||
attribute_name_duplicate = "重复的属性名 {}"
|
||||
add_attribute_failed = "创建属性 {} 失败!"
|
||||
update_attribute_failed = "修改属性 {} 失败!"
|
||||
cannot_edit_attribute = "您没有权限修改该属性!"
|
||||
cannot_delete_attribute = "您没有权限删除该属性!"
|
||||
attribute_name_cannot_be_builtin = "属性字段名不能是内置字段: id, _id, ci_id, type, _type, ci_type"
|
||||
|
||||
ci_not_found = "CI {} 不存在"
|
||||
unique_constraint = "多属性联合唯一校验不通过: {}"
|
||||
unique_value_not_found = "模型的主键 {} 不存在!"
|
||||
unique_key_required = "主键字段 {} 缺失"
|
||||
ci_is_already_existed = "CI 已经存在!"
|
||||
relation_constraint = "关系约束: {}, 校验失败 "
|
||||
relation_not_found = "CI关系: {} 不存在"
|
||||
ci_search_Parentheses_invalid = "搜索表达式里小括号前不支持: 或、非"
|
||||
|
||||
ci_type_not_found2 = "模型 {} 不存在"
|
||||
ci_type_is_already_existed = "模型 {} 已经存在"
|
||||
unique_key_not_define = "主键未定义或者已被删除"
|
||||
only_owner_can_delete = "只有创建人才能删除它!"
|
||||
ci_exists_and_cannot_delete_type = "因为CI已经存在,不能删除模型"
|
||||
ci_type_group_not_found = "模型分组 {} 不存在"
|
||||
ci_type_group_exists = "模型分组 {} 已经存在"
|
||||
ci_type_relation_not_found = "模型关系 {} 不存在"
|
||||
ci_type_attribute_group_duplicate = "属性分组 {} 已存在"
|
||||
ci_type_attribute_group_not_found = "属性分组 {} 不存在"
|
||||
ci_type_group_attribute_not_found = "属性组<{0}> - 属性<{1}> 不存在"
|
||||
unique_constraint_duplicate = "唯一约束已经存在!"
|
||||
unique_constraint_invalid = "唯一约束的属性不能是 JSON 和 多值"
|
||||
ci_type_trigger_duplicate = "重复的触发器"
|
||||
ci_type_trigger_not_found = "触发器 {} 不存在"
|
||||
|
||||
record_not_found = "操作记录 {} 不存在"
|
||||
cannot_delete_unique = "不能删除唯一标识"
|
||||
cannot_delete_default_order_attr = "不能删除默认排序的属性"
|
||||
|
||||
preference_relation_view_node_required = "没有选择节点"
|
||||
preference_search_option_not_found = "该搜索选项不存在!"
|
||||
preference_search_option_exists = "该搜索选项命名重复!"
|
||||
|
||||
relation_type_exists = "关系类型 {} 已经存在"
|
||||
relation_type_not_found = "关系类型 {} 不存在"
|
||||
|
||||
attribute_value_invalid = "无效的属性值: {}"
|
||||
attribute_value_invalid2 = "{} 无效的值: {}"
|
||||
not_in_choice_values = "{} 不在预定义值里"
|
||||
attribute_value_unique_required = "属性 {} 的值必须是唯一的, 当前值 {} 已存在"
|
||||
attribute_value_required = "属性 {} 值必须存在"
|
||||
attribute_value_unknown_error = "新增或者修改属性值未知错误: {}"
|
||||
|
||||
custom_name_duplicate = "订制名重复"
|
||||
|
||||
limit_ci_type = "模型数超过限制: {}"
|
||||
limit_ci = "CI数超过限制: {}"
|
||||
|
||||
adr_duplicate = "自动发现规则: {} 已经存在!"
|
||||
adr_not_found = "自动发现规则: {} 不存在!"
|
||||
adr_referenced = "该自动发现规则被模型引用, 不能删除!"
|
||||
ad_duplicate = "自动发现规则的应用不能重复定义!"
|
||||
ad_not_found = "您要修改的自动发现: {} 不存在!"
|
||||
ad_not_unique_key = "属性字段没有包括唯一标识: {}"
|
||||
adc_not_found = "自动发现的实例不存在!"
|
||||
adt_not_found = "模型并未关联该自动发现!"
|
||||
adt_secret_no_permission = "只有创建人才能修改Secret!"
|
||||
cannot_delete_adt = "该规则已经有自动发现的实例, 不能被删除!"
|
||||
adr_default_ref_once = "该默认的自动发现规则 已经被模型 {} 引用!"
|
||||
adr_unique_key_required = "unique_key方法必须返回非空字符串!"
|
||||
adr_plugin_attributes_list_required = "attributes方法必须返回的是list"
|
||||
adr_plugin_attributes_list_no_empty = "attributes方法返回的list不能为空!"
|
||||
adt_target_all_no_permission = "只有管理员才可以定义执行机器为: 所有节点!"
|
||||
adt_target_expr_no_permission = "执行机器权限检查不通过: {}"
|
||||
|
||||
ci_filter_name_cannot_be_empty = "CI过滤授权 必须命名!"
|
||||
ci_filter_perm_cannot_or_query = "CI过滤授权 暂时不支持 或 查询"
|
||||
ci_filter_perm_attr_no_permission = "您没有属性 {} 的操作权限!"
|
||||
ci_filter_perm_ci_no_permission = "您没有该CI的操作权限!"
|
@@ -1,3 +1,25 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
__all__ = ['db', 'es']
|
||||
__all__ = ['db', 'es', 'search']
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
|
||||
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
|
||||
|
||||
def search(query=None,
|
||||
fl=None,
|
||||
facet=None,
|
||||
page=1,
|
||||
ret_key=RetKey.NAME,
|
||||
count=1,
|
||||
sort=None,
|
||||
excludes=None):
|
||||
if current_app.config.get("USE_ES"):
|
||||
s = SearchFromES(query, fl, facet, page, ret_key, count, sort)
|
||||
else:
|
||||
s = SearchFromDB(query, fl, facet, page, ret_key, count, sort, excludes=excludes)
|
||||
|
||||
return s
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
QUERY_CIS_BY_VALUE_TABLE = """
|
||||
SELECT attr.name AS attr_name,
|
||||
attr.alias AS attr_alias,
|
||||
@@ -58,9 +57,51 @@ QUERY_CI_BY_ATTR_NAME = """
|
||||
AND {0}.value {2}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_ID = """
|
||||
SELECT c_cis.id as ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.id={}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
SELECT c_cis.id AS ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.type_id in ({0})
|
||||
"""
|
||||
|
||||
QUERY_UNION_CI_ATTRIBUTE_IS_NULL = """
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT c_cis.id AS ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.type_id IN ({0})
|
||||
) {3}
|
||||
LEFT JOIN (
|
||||
SELECT {1}.ci_id
|
||||
FROM {1}
|
||||
WHERE {1}.attr_id = {2}
|
||||
AND {1}.value LIKE "%"
|
||||
) {4} USING (ci_id)
|
||||
WHERE {4}.ci_id IS NULL
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_NO_ATTR = """
|
||||
SELECT *
|
||||
FROM
|
||||
(SELECT c_value_index_texts.ci_id
|
||||
FROM c_value_index_texts
|
||||
WHERE c_value_index_texts.value LIKE "{0}"
|
||||
UNION
|
||||
SELECT c_value_index_integers.ci_id
|
||||
FROM c_value_index_integers
|
||||
WHERE c_value_index_integers.value LIKE "{0}"
|
||||
UNION
|
||||
SELECT c_value_index_floats.ci_id
|
||||
FROM c_value_index_floats
|
||||
WHERE c_value_index_floats.value LIKE "{0}"
|
||||
UNION
|
||||
SELECT c_value_index_datetime.ci_id
|
||||
FROM c_value_index_datetime
|
||||
WHERE c_value_index_datetime.value LIKE "{0}") AS {1}
|
||||
GROUP BY {1}.ci_id
|
||||
"""
|
||||
|
@@ -3,23 +3,33 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import time
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from flask import g
|
||||
from jinja2 import Template
|
||||
from api.extensions import db
|
||||
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.const import PermEnum
|
||||
from api.lib.cmdb.const import ResourceTypeEnum
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.perms import CIFilterPermsCRUD
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.cmdb.search.ci.db.query_sql import FACET_QUERY
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ATTR_NAME
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_ID
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_NO_ATTR
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_CI_BY_TYPE
|
||||
from api.lib.cmdb.search.ci.db.query_sql import QUERY_UNION_CI_ATTRIBUTE_IS_NULL
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.perm.acl.acl import ACLManager
|
||||
from api.lib.perm.acl.acl import is_app_admin
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
|
||||
|
||||
class Search(object):
|
||||
@@ -30,9 +40,11 @@ class Search(object):
|
||||
ret_key=RetKey.NAME,
|
||||
count=1,
|
||||
sort=None,
|
||||
ci_ids=None):
|
||||
ci_ids=None,
|
||||
excludes=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
self.facet_field = facet_field
|
||||
self.page = page
|
||||
self.ret_key = ret_key
|
||||
@@ -43,11 +55,17 @@ class Search(object):
|
||||
self.type_id_list = []
|
||||
self.only_type_query = False
|
||||
|
||||
self.valid_type_names = []
|
||||
self.type2filter_perms = dict()
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
operator = "&"
|
||||
if key.startswith("+"):
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("-~"):
|
||||
operator = "|~"
|
||||
key = key[2:].strip()
|
||||
elif key.startswith("-"):
|
||||
operator = "|"
|
||||
key = key[1:].strip()
|
||||
@@ -70,14 +88,41 @@ class Search(object):
|
||||
if attr:
|
||||
return attr.name, attr.value_type, operator, attr
|
||||
else:
|
||||
raise SearchError("{0} is not existed".format(key))
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(key))
|
||||
|
||||
def _type_query_handler(self, v):
|
||||
def _type_query_handler(self, v, queries):
|
||||
new_v = v[1:-1].split(";") if v.startswith("(") and v.endswith(")") else [v]
|
||||
for _v in new_v:
|
||||
ci_type = CITypeCache.get(_v)
|
||||
|
||||
if len(new_v) == 1 and not self.sort and ci_type.default_order_attr:
|
||||
self.sort = ci_type.default_order_attr
|
||||
|
||||
if ci_type is not None:
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
if self.valid_type_names == "ALL" or ci_type.name in self.valid_type_names:
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
if ci_type.id in self.type2filter_perms:
|
||||
ci_filter = self.type2filter_perms[ci_type.id].get('ci_filter')
|
||||
if ci_filter:
|
||||
sub = []
|
||||
ci_filter = Template(ci_filter).render(user=g.user)
|
||||
for i in ci_filter.split(','):
|
||||
if i.startswith("~") and not sub:
|
||||
queries.append(i)
|
||||
else:
|
||||
sub.append(i)
|
||||
if sub:
|
||||
queries.append(dict(operator="&", queries=sub))
|
||||
|
||||
if self.type2filter_perms[ci_type.id].get('attr_filter'):
|
||||
if not self.fl:
|
||||
self.fl = set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
else:
|
||||
self.fl = set(self.fl) & set(self.type2filter_perms[ci_type.id]['attr_filter'])
|
||||
else:
|
||||
raise SearchError(ErrFormat.no_permission.format(ci_type.alias, PermEnum.READ))
|
||||
else:
|
||||
raise SearchError(ErrFormat.ci_type_not_found2.format(_v))
|
||||
|
||||
if self.type_id_list:
|
||||
type_ids = ",".join(self.type_id_list)
|
||||
@@ -89,24 +134,32 @@ class Search(object):
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _in_query_handler(attr, v):
|
||||
def _id_query_handler(v):
|
||||
return QUERY_CI_BY_ID.format(v)
|
||||
|
||||
@staticmethod
|
||||
def _in_query_handler(attr, v, is_not):
|
||||
new_v = v[1:-1].split(";")
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
in_query = " OR {0}.value ".format(table_name).join(['LIKE "{0}"'.format(_v.replace("*", "%")) for _v in new_v])
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
in_query = " OR {0}.value ".format(table_name).join(['{0} "{1}"'.format(
|
||||
"NOT LIKE" if is_not else "LIKE",
|
||||
_v.replace("*", "%")) for _v in new_v])
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
def _range_query_handler(attr, v):
|
||||
def _range_query_handler(attr, v, is_not):
|
||||
start, end = [x.strip() for x in v[1:-1].split("_TO_")]
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
range_query = "BETWEEN '{0}' AND '{1}'".format(start.replace("*", "%"), end.replace("*", "%"))
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
range_query = "{0} '{1}' AND '{2}'".format(
|
||||
"NOT BETWEEN" if is_not else "BETWEEN",
|
||||
start.replace("*", "%"), end.replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
def _comparison_query_handler(attr, v):
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
if v.startswith(">=") or v.startswith("<="):
|
||||
comparison_query = "{0} '{1}'".format(v[:2], v[2:].replace("*", "%"))
|
||||
else:
|
||||
@@ -154,18 +207,44 @@ class Search(object):
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id "
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format((self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
def __sort_by_type(self, sort_type, query_sql):
|
||||
ret_sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id FROM ({0}) AS B {1}"
|
||||
|
||||
if self.type_id_list:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({0}) ".format(
|
||||
",".join(self.type_id_list)))
|
||||
|
||||
return ret_sql.format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id IN ({3}) "
|
||||
"ORDER BY c_cis.type_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count, ",".join(self.type_id_list)))
|
||||
|
||||
else:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id ")
|
||||
|
||||
return ret_sql.format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id "
|
||||
"ORDER BY c_cis.type_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
def __sort_by_field(self, field, sort_type, query_sql):
|
||||
attr = AttributeCache.get(field)
|
||||
attr_id = attr.id
|
||||
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
||||
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
||||
new_table = _v_query_sql
|
||||
|
||||
if self.only_type_query or not self.type_id_list:
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id, C.value " \
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \
|
||||
"FROM ({0}) AS C " \
|
||||
"ORDER BY C.value {2} " \
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count)
|
||||
@@ -176,7 +255,7 @@ class Search(object):
|
||||
INNER JOIN c_cis on c_cis.id=C.ci_id
|
||||
WHERE c_cis.type_id IN ({1})""".format(new_table, ",".join(self.type_id_list))
|
||||
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id, C.value
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id
|
||||
FROM ({0}) AS C
|
||||
INNER JOIN c_cis on c_cis.id=C.ci_id
|
||||
WHERE c_cis.type_id IN ({4})
|
||||
@@ -192,6 +271,8 @@ class Search(object):
|
||||
|
||||
if field in ("_id", "ci_id") or not field:
|
||||
return self.__sort_by_id(sort_type, query_sql)
|
||||
elif field in ("_type", "ci_type"):
|
||||
return self.__sort_by_type(sort_type, query_sql)
|
||||
else:
|
||||
return self.__sort_by_field(field, sort_type, query_sql)
|
||||
|
||||
@@ -201,7 +282,7 @@ class Search(object):
|
||||
query_sql = """SELECT * FROM ({0}) as {1}
|
||||
INNER JOIN ({2}) as {3} USING(ci_id)""".format(query_sql, alias, _query_sql, alias + "A")
|
||||
|
||||
elif operator == "|":
|
||||
elif operator == "|" or operator == "|~":
|
||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
||||
|
||||
elif operator == "~":
|
||||
@@ -225,54 +306,142 @@ class Search(object):
|
||||
|
||||
return numfound, res
|
||||
|
||||
def __get_types_has_read(self):
|
||||
"""
|
||||
:return: _type:(type1;type2)
|
||||
"""
|
||||
acl = ACLManager('cmdb')
|
||||
res = acl.get_resources(ResourceTypeEnum.CI)
|
||||
|
||||
self.valid_type_names = {i['name'] for i in res if PermEnum.READ in i['permissions']}
|
||||
|
||||
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
|
||||
if res2:
|
||||
self.type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
|
||||
|
||||
return "_type:({})".format(";".join(self.valid_type_names))
|
||||
|
||||
def __confirm_type_first(self, queries):
|
||||
|
||||
has_type = False
|
||||
|
||||
result = []
|
||||
sub = {}
|
||||
id_query = None
|
||||
for q in queries:
|
||||
if q.startswith("_type"):
|
||||
queries.remove(q)
|
||||
queries.insert(0, q)
|
||||
has_type = True
|
||||
result.insert(0, q)
|
||||
if len(queries) == 1 or queries[1].startswith("-") or queries[1].startswith("~"):
|
||||
self.only_type_query = True
|
||||
return queries
|
||||
elif q.startswith("_id") and len(q.split(':')) == 2:
|
||||
id_query = int(q.split(":")[1]) if q.split(":")[1].isdigit() else None
|
||||
result.append(q)
|
||||
elif q.startswith("(") or q[1:].startswith("(") or q[2:].startswith("("):
|
||||
if not q.startswith("("):
|
||||
raise SearchError(ErrFormat.ci_search_Parentheses_invalid)
|
||||
|
||||
def __query_build_by_field(self, queries):
|
||||
query_sql, alias, operator = "", "A", "&"
|
||||
is_first, only_type_query_special = True, True
|
||||
operator, q = self._operator_proc(q)
|
||||
if q.endswith(")"):
|
||||
result.append(dict(operator=operator, queries=[q[1:-1]]))
|
||||
|
||||
sub = dict(operator=operator, queries=[q[1:]])
|
||||
elif q.endswith(")") and sub:
|
||||
sub['queries'].append(q[:-1])
|
||||
result.append(copy.deepcopy(sub))
|
||||
sub = {}
|
||||
elif sub:
|
||||
sub['queries'].append(q)
|
||||
else:
|
||||
result.append(q)
|
||||
|
||||
_is_app_admin = is_app_admin('cmdb') or g.user.username == "worker"
|
||||
if result and not has_type and not _is_app_admin:
|
||||
type_q = self.__get_types_has_read()
|
||||
if id_query:
|
||||
ci = CIManager.get_by_id(id_query)
|
||||
if not ci:
|
||||
raise SearchError(ErrFormat.ci_not_found.format(id_query))
|
||||
result.insert(0, "_type:{}".format(ci.type_id))
|
||||
else:
|
||||
result.insert(0, type_q)
|
||||
elif _is_app_admin:
|
||||
self.valid_type_names = "ALL"
|
||||
else:
|
||||
self.__get_types_has_read()
|
||||
|
||||
current_app.logger.warning(result)
|
||||
|
||||
return result
|
||||
|
||||
def __query_by_attr(self, q, queries, alias):
|
||||
k = q.split(":")[0].strip()
|
||||
v = "\:".join(q.split(":")[1:]).strip()
|
||||
v = v.replace("'", "\\'")
|
||||
v = v.replace('"', '\\"')
|
||||
field, field_type, operator, attr = self._attr_name_proc(k)
|
||||
if field == "_type":
|
||||
_query_sql = self._type_query_handler(v, queries)
|
||||
|
||||
elif field == "_id":
|
||||
_query_sql = self._id_query_handler(v)
|
||||
|
||||
elif field:
|
||||
if attr is None:
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
||||
|
||||
is_not = True if operator == "|~" else False
|
||||
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
_query_sql = self._in_query_handler(attr, v, is_not)
|
||||
# range query
|
||||
elif v.startswith("[") and v.endswith("]") and "_TO_" in v:
|
||||
_query_sql = self._range_query_handler(attr, v, is_not)
|
||||
# comparison query
|
||||
elif v.startswith(">=") or v.startswith("<=") or v.startswith(">") or v.startswith("<"):
|
||||
_query_sql = self._comparison_query_handler(attr, v)
|
||||
else:
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
if is_not and v == "*" and self.type_id_list: # special handle
|
||||
_query_sql = QUERY_UNION_CI_ATTRIBUTE_IS_NULL.format(
|
||||
",".join(self.type_id_list),
|
||||
table_name,
|
||||
attr.id,
|
||||
alias,
|
||||
alias + 'A'
|
||||
)
|
||||
alias += "AA"
|
||||
else:
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name,
|
||||
attr.id,
|
||||
'{0} "{1}"'.format("NOT LIKE" if is_not else "LIKE", v.replace("*", "%")))
|
||||
else:
|
||||
raise SearchError(ErrFormat.argument_invalid.format("q"))
|
||||
|
||||
return alias, _query_sql, operator
|
||||
|
||||
def __query_build_by_field(self, queries, is_first=True, only_type_query_special=True, alias='A', operator='&'):
|
||||
query_sql = ""
|
||||
|
||||
for q in queries:
|
||||
_query_sql = ""
|
||||
if ":" in q:
|
||||
k = q.split(":")[0].strip()
|
||||
v = "\:".join(q.split(":")[1:]).strip()
|
||||
current_app.logger.debug(v)
|
||||
field, field_type, operator, attr = self._attr_name_proc(k)
|
||||
if field == "_type":
|
||||
_query_sql = self._type_query_handler(v)
|
||||
current_app.logger.debug(_query_sql)
|
||||
elif field == "_id": # exclude all others
|
||||
ci = CI.get_by_id(v)
|
||||
if ci is not None:
|
||||
return 1, [str(v)]
|
||||
elif field:
|
||||
if attr is None:
|
||||
raise SearchError("{0} is not found".format(field))
|
||||
if isinstance(q, dict):
|
||||
alias, _query_sql, operator = self.__query_build_by_field(q['queries'], True, True, alias)
|
||||
current_app.logger.info(_query_sql)
|
||||
current_app.logger.info((operator, is_first, alias))
|
||||
operator = q['operator']
|
||||
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
_query_sql = self._in_query_handler(attr, v)
|
||||
# range query
|
||||
elif v.startswith("[") and v.endswith("]") and "_TO_" in v:
|
||||
_query_sql = self._range_query_handler(attr, v)
|
||||
# comparison query
|
||||
elif v.startswith(">=") or v.startswith("<=") or v.startswith(">") or v.startswith("<"):
|
||||
_query_sql = self._comparison_query_handler(attr, v)
|
||||
else:
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.id, 'LIKE "{0}"'.format(v.replace("*", "%")))
|
||||
else:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
elif ":" in q and not q.startswith("*"):
|
||||
alias, _query_sql, operator = self.__query_by_attr(q, queries, alias)
|
||||
elif q == "*":
|
||||
continue
|
||||
elif q:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
q = q.replace("'", "\\'")
|
||||
q = q.replace('"', '\\"')
|
||||
q = q.replace("*", "%").replace('\\n', '%')
|
||||
_query_sql = QUERY_CI_BY_NO_ATTR.format(q, alias)
|
||||
|
||||
if is_first and _query_sql and not self.only_type_query:
|
||||
query_sql = "SELECT * FROM ({0}) AS {1}".format(_query_sql, alias)
|
||||
@@ -285,7 +454,8 @@ class Search(object):
|
||||
elif _query_sql:
|
||||
query_sql = self._wrap_sql(operator, alias, _query_sql, query_sql)
|
||||
alias += "AA"
|
||||
return None, query_sql
|
||||
|
||||
return alias, query_sql, operator
|
||||
|
||||
def _filter_ids(self, query_sql):
|
||||
if self.ci_ids:
|
||||
@@ -294,21 +464,36 @@ class Search(object):
|
||||
|
||||
return query_sql
|
||||
|
||||
@staticmethod
|
||||
def _extra_handle_query_expr(args): # \, or ,
|
||||
result = []
|
||||
if args:
|
||||
result.append(args[0])
|
||||
|
||||
for arg in args[1:]:
|
||||
if result[-1].endswith('\\'):
|
||||
result[-1] = ",".join([result[-1].rstrip('\\'), arg])
|
||||
# elif ":" not in arg:
|
||||
# result[-1] = ",".join([result[-1], arg])
|
||||
else:
|
||||
result.append(arg)
|
||||
|
||||
return result
|
||||
|
||||
def _query_build_raw(self):
|
||||
|
||||
queries = handle_arg_list(self.orig_query)
|
||||
queries = self._extra_handle_query_expr(queries)
|
||||
queries = self.__confirm_type_first(queries)
|
||||
current_app.logger.debug(queries)
|
||||
|
||||
ret, query_sql = self.__query_build_by_field(queries)
|
||||
if ret is not None:
|
||||
return ret, query_sql
|
||||
_, query_sql, _ = self.__query_build_by_field(queries)
|
||||
|
||||
s = time.time()
|
||||
if query_sql:
|
||||
query_sql = self._filter_ids(query_sql)
|
||||
self.query_sql = query_sql
|
||||
current_app.logger.debug(query_sql)
|
||||
# current_app.logger.debug(query_sql)
|
||||
numfound, res = self._execute_sql(query_sql)
|
||||
current_app.logger.debug("query ci ids is: {0}".format(time.time() - s))
|
||||
return numfound, [_res[0] for _res in res]
|
||||
@@ -320,9 +505,9 @@ class Search(object):
|
||||
for f in self.facet_field:
|
||||
k, field_type, _, attr = self._attr_name_proc(f)
|
||||
if k:
|
||||
table_name = TableMap(attr_name=k).table_name
|
||||
table_name = TableMap(attr=attr).table_name
|
||||
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
|
||||
current_app.logger.debug(query_sql)
|
||||
# current_app.logger.debug(query_sql)
|
||||
result = db.session.execute(query_sql).fetchall()
|
||||
facet[k] = result
|
||||
|
||||
@@ -356,7 +541,7 @@ class Search(object):
|
||||
|
||||
response, counter = [], {}
|
||||
if ci_ids:
|
||||
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl)
|
||||
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl, excludes=self.excludes)
|
||||
for res in response:
|
||||
ci_type = res.get("ci_type")
|
||||
if ci_type not in counter.keys():
|
||||
|
@@ -10,6 +10,7 @@ from api.extensions import es
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.utils import handle_arg_list
|
||||
|
||||
@@ -22,9 +23,11 @@ class Search(object):
|
||||
ret_key=RetKey.NAME,
|
||||
count=1,
|
||||
sort=None,
|
||||
ci_ids=None):
|
||||
ci_ids=None,
|
||||
excludes=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.fl = fl or []
|
||||
self.excludes = excludes or []
|
||||
self.facet_field = facet_field
|
||||
self.page = page
|
||||
self.ret_key = ret_key
|
||||
@@ -39,6 +42,9 @@ class Search(object):
|
||||
operator = "&"
|
||||
if key.startswith("+"):
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("-~"):
|
||||
operator = "|~"
|
||||
key = key[2:].strip()
|
||||
elif key.startswith("-"):
|
||||
operator = "|"
|
||||
key = key[1:].strip()
|
||||
@@ -53,6 +59,8 @@ class Search(object):
|
||||
return self.query['query']['bool']['must']
|
||||
elif operator == "|":
|
||||
return self.query['query']['bool']['should']
|
||||
elif operator == "|~":
|
||||
return self.query['query']['bool']['should']
|
||||
else:
|
||||
return self.query['query']['bool']['must_not']
|
||||
|
||||
@@ -69,9 +77,9 @@ class Search(object):
|
||||
if attr:
|
||||
return attr.name, attr.value_type, operator
|
||||
else:
|
||||
raise SearchError("{0} is not existed".format(key))
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(key))
|
||||
|
||||
def _in_query_handle(self, attr, v):
|
||||
def _in_query_handle(self, attr, v, is_not):
|
||||
terms = v[1:-1].split(";")
|
||||
operator = "|"
|
||||
if attr in ('_type', 'ci_type', 'type_id') and terms and terms[0].isdigit():
|
||||
@@ -79,11 +87,27 @@ class Search(object):
|
||||
terms = map(int, terms)
|
||||
current_app.logger.warning(terms)
|
||||
for term in terms:
|
||||
self._operator2query(operator).append({
|
||||
"term": {
|
||||
attr: term
|
||||
}
|
||||
})
|
||||
if is_not:
|
||||
self._operator2query(operator).append({
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{
|
||||
"term": {
|
||||
attr: term
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
else:
|
||||
self._operator2query(operator).append({
|
||||
"term": {
|
||||
attr: term
|
||||
}
|
||||
})
|
||||
|
||||
def _filter_ids(self):
|
||||
if self.ci_ids:
|
||||
@@ -95,18 +119,36 @@ class Search(object):
|
||||
return int(float(s))
|
||||
return s
|
||||
|
||||
def _range_query_handle(self, attr, v, operator):
|
||||
def _range_query_handle(self, attr, v, operator, is_not):
|
||||
left, right = v.split("_TO_")
|
||||
left, right = left.strip()[1:], right.strip()[:-1]
|
||||
self._operator2query(operator).append({
|
||||
"range": {
|
||||
attr: {
|
||||
"lte": self._digit(right),
|
||||
"gte": self._digit(left),
|
||||
"boost": 2.0
|
||||
if is_not:
|
||||
self._operator2query(operator).append({
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{
|
||||
"range": {
|
||||
attr: {
|
||||
"lte": self._digit(right),
|
||||
"gte": self._digit(left),
|
||||
"boost": 2.0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
else:
|
||||
self._operator2query(operator).append({
|
||||
"range": {
|
||||
attr: {
|
||||
"lte": self._digit(right),
|
||||
"gte": self._digit(left),
|
||||
"boost": 2.0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def _comparison_query_handle(self, attr, v, operator):
|
||||
if v.startswith(">="):
|
||||
@@ -126,21 +168,50 @@ class Search(object):
|
||||
}
|
||||
})
|
||||
|
||||
def _match_query_handle(self, attr, v, operator):
|
||||
def _match_query_handle(self, attr, v, operator, is_not):
|
||||
if "*" in v:
|
||||
self._operator2query(operator).append({
|
||||
"wildcard": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
})
|
||||
if is_not:
|
||||
self._operator2query(operator).append({
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{
|
||||
"wildcard": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
})
|
||||
else:
|
||||
self._operator2query(operator).append({
|
||||
"wildcard": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
})
|
||||
else:
|
||||
if attr == "ci_type" and v.isdigit():
|
||||
attr = "type_id"
|
||||
self._operator2query(operator).append({
|
||||
"term": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
})
|
||||
|
||||
if is_not:
|
||||
self._operator2query(operator).append({
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{
|
||||
"term": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
})
|
||||
else:
|
||||
self._operator2query(operator).append({
|
||||
"term": {
|
||||
attr: v.lower() if isinstance(v, six.string_types) else v
|
||||
}
|
||||
})
|
||||
|
||||
def __query_build_by_field(self, queries):
|
||||
|
||||
@@ -150,21 +221,23 @@ class Search(object):
|
||||
v = ":".join(q.split(":")[1:]).strip()
|
||||
field_name, field_type, operator = self._attr_name_proc(k)
|
||||
if field_name:
|
||||
is_not = True if operator == "|~" else False
|
||||
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
self._in_query_handle(field_name, v)
|
||||
self._in_query_handle(field_name, v, is_not)
|
||||
# range query
|
||||
elif v.startswith("[") and v.endswith("]") and "_TO_" in v:
|
||||
self._range_query_handle(field_name, v, operator)
|
||||
self._range_query_handle(field_name, v, operator, is_not)
|
||||
# comparison query
|
||||
elif v.startswith(">=") or v.startswith("<=") or v.startswith(">") or v.startswith("<"):
|
||||
self._comparison_query_handle(field_name, v, operator)
|
||||
else:
|
||||
self._match_query_handle(field_name, v, operator)
|
||||
self._match_query_handle(field_name, v, operator, is_not)
|
||||
else:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
raise SearchError(ErrFormat.argument_invalid.format("q"))
|
||||
elif q:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
raise SearchError(ErrFormat.argument_invalid.format("q"))
|
||||
|
||||
def _query_build_raw(self):
|
||||
|
||||
@@ -191,7 +264,7 @@ class Search(object):
|
||||
for field in self.facet_field:
|
||||
attr = AttributeCache.get(field)
|
||||
if not attr:
|
||||
raise SearchError("Facet by <{0}> does not exist".format(field))
|
||||
raise SearchError(ErrFormat.attribute_not_found(field))
|
||||
aggregations['aggs'].update({
|
||||
field: {
|
||||
"terms": {
|
||||
@@ -222,7 +295,7 @@ class Search(object):
|
||||
|
||||
attr = AttributeCache.get(field)
|
||||
if not attr:
|
||||
raise SearchError("Sort by <{0}> does not exist".format(field))
|
||||
raise SearchError(ErrFormat.attribute_not_found.format(field))
|
||||
|
||||
sort_by = "{0}.keyword".format(field) \
|
||||
if attr.value_type not in (ValueTypeEnum.INT, ValueTypeEnum.FLOAT) else field
|
||||
@@ -242,7 +315,7 @@ class Search(object):
|
||||
numfound, cis, facet = self._query_build_raw()
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
raise SearchError("unknown search error")
|
||||
raise SearchError(ErrFormat.unknown_search_error)
|
||||
|
||||
total = len(cis)
|
||||
|
||||
|
@@ -2,13 +2,16 @@
|
||||
|
||||
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
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.lib.cmdb.search.ci.es.search import Search as SearchFromES
|
||||
from api.models.cmdb import CI
|
||||
@@ -22,7 +25,8 @@ class Search(object):
|
||||
facet_field=None,
|
||||
page=1,
|
||||
count=None,
|
||||
sort=None):
|
||||
sort=None,
|
||||
reverse=False):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
@@ -32,20 +36,35 @@ class Search(object):
|
||||
|
||||
self.root_id = root_id
|
||||
self.level = level
|
||||
self.reverse = reverse
|
||||
|
||||
def _get_ids(self):
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
def _get_reverse_ids(self):
|
||||
merge_ids = []
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for level in range(1, sorted(self.level)[-1] + 1):
|
||||
ids = CIRelationManager.get_ancestor_ids(ids, 1)
|
||||
if level in self.level:
|
||||
merge_ids.extend(ids)
|
||||
|
||||
return merge_ids
|
||||
|
||||
def search(self):
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
cis = [CI.get_by_id(_id) or abort(404, "CI <{0}> does not exist".format(_id)) for _id in ids]
|
||||
cis = [CI.get_by_id(_id) or abort(404, ErrFormat.ci_not_found.format("id={}".format(_id))) for _id in ids]
|
||||
|
||||
merge_ids = []
|
||||
for level in self.level:
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for _ in range(0, level):
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
filter(lambda x: x is not None, rd.get(ids, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
ids = [j for i in _tmp for j in i]
|
||||
|
||||
merge_ids.extend(ids)
|
||||
merge_ids = self._get_ids() if not self.reverse else self._get_reverse_ids()
|
||||
|
||||
if not self.orig_query or ("_type:" not in self.orig_query
|
||||
and "type_id:" not in self.orig_query
|
||||
@@ -53,7 +72,10 @@ class Search(object):
|
||||
type_ids = []
|
||||
for level in self.level:
|
||||
for ci in cis:
|
||||
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
||||
if not self.reverse:
|
||||
type_ids.extend(CITypeRelationManager.get_child_type_ids(ci.type_id, level))
|
||||
else:
|
||||
type_ids.extend(CITypeRelationManager.get_parent_type_ids(ci.type_id, level))
|
||||
type_ids = list(set(type_ids))
|
||||
if self.orig_query:
|
||||
self.orig_query = "_type:({0}),{1}".format(";".join(list(map(str, type_ids))), self.orig_query)
|
||||
@@ -86,24 +108,30 @@ class Search(object):
|
||||
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
|
||||
for l in range(0, int(self.level)):
|
||||
if not l:
|
||||
_tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
_tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
[i or '{}' for i in rd.get(ids, REDIS_PREFIX_CI_RELATION) or []]))
|
||||
else:
|
||||
for idx, i in enumerate(_tmp):
|
||||
if i:
|
||||
for idx, item in enumerate(_tmp):
|
||||
if item:
|
||||
if type_ids and l == self.level - 1:
|
||||
__tmp = list(
|
||||
map(lambda x: list({_id: 1 for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids}.keys()),
|
||||
map(lambda x: [(_id, type_id) for _id, type_id in json.loads(x).items()
|
||||
if type_id in type_ids],
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(i, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
else:
|
||||
|
||||
__tmp = list(map(lambda x: list(json.loads(x).keys()),
|
||||
__tmp = list(map(lambda x: list(json.loads(x).items()),
|
||||
filter(lambda x: x is not None,
|
||||
rd.get(i, REDIS_PREFIX_CI_RELATION) or [])))
|
||||
rd.get([i[0] for i in item], REDIS_PREFIX_CI_RELATION) or [])))
|
||||
|
||||
_tmp[idx] = [j for i in __tmp for j in i]
|
||||
else:
|
||||
_tmp[idx] = []
|
||||
|
||||
return {_id: len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||
result = {str(_id): len(_tmp[idx]) for idx, _id in enumerate(ids)}
|
||||
|
||||
result.update(
|
||||
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
|
||||
|
||||
return result
|
||||
|
@@ -52,7 +52,7 @@ class ValueTypeMap(object):
|
||||
ValueTypeEnum.FLOAT: float,
|
||||
ValueTypeEnum.TEXT: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.TIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.DATE: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.DATE: lambda x: (x.decode() if not isinstance(x, six.string_types) else x).split()[0],
|
||||
ValueTypeEnum.DATETIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
|
||||
}
|
||||
@@ -109,17 +109,25 @@ class ValueTypeMap(object):
|
||||
|
||||
|
||||
class TableMap(object):
|
||||
def __init__(self, attr_name=None):
|
||||
def __init__(self, attr_name=None, attr=None, is_index=None):
|
||||
self.attr_name = attr_name
|
||||
self.attr = attr
|
||||
self.is_index = is_index
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
attr = AttributeCache.get(self.attr_name)
|
||||
i = "index_{0}".format(attr.value_type) if attr.is_index else attr.value_type
|
||||
attr = AttributeCache.get(self.attr_name) if not self.attr else self.attr
|
||||
if self.is_index is None:
|
||||
self.is_index = attr.is_index
|
||||
i = "index_{0}".format(attr.value_type) if self.is_index else attr.value_type
|
||||
|
||||
return ValueTypeMap.table.get(i)
|
||||
|
||||
@property
|
||||
def table_name(self):
|
||||
attr = AttributeCache.get(self.attr_name)
|
||||
i = "index_{0}".format(attr.value_type) if attr.is_index else attr.value_type
|
||||
attr = AttributeCache.get(self.attr_name) if not self.attr else self.attr
|
||||
if self.is_index is None:
|
||||
self.is_index = attr.is_index
|
||||
i = "index_{0}".format(attr.value_type) if self.is_index else attr.value_type
|
||||
|
||||
return ValueTypeMap.table_name.get(i)
|
||||
|
@@ -3,7 +3,16 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import imp
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import jinja2
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from jinja2schema import infer
|
||||
from jinja2schema import to_json_schema
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
@@ -13,9 +22,11 @@ from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import ValueTypeEnum
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.resp_format import ErrFormat
|
||||
from api.lib.cmdb.utils import TableMap
|
||||
from api.lib.cmdb.utils import ValueTypeMap
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
|
||||
|
||||
class AttributeValueManager(object):
|
||||
@@ -50,7 +61,7 @@ class AttributeValueManager(object):
|
||||
if not attr:
|
||||
continue
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
value_table = TableMap(attr=attr).table
|
||||
rs = value_table.get_by(ci_id=ci_id,
|
||||
attr_id=attr.id,
|
||||
use_master=use_master,
|
||||
@@ -64,6 +75,7 @@ class AttributeValueManager(object):
|
||||
|
||||
if unique_key is not None and attr.id == unique_key.id and rs:
|
||||
res['unique'] = unique_key.name
|
||||
res['unique_alias'] = unique_key.alias
|
||||
|
||||
return res
|
||||
|
||||
@@ -76,48 +88,213 @@ class AttributeValueManager(object):
|
||||
v = deserialize(value)
|
||||
return v
|
||||
except ValueError:
|
||||
return abort(400, "attribute value <{0}> is invalid".format(value))
|
||||
return abort(400, ErrFormat.attribute_value_invalid.format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_choice(attr, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type)
|
||||
if value not in choice_values:
|
||||
return abort(400, "{0} does not existed in choice values".format(value))
|
||||
choice_values = AttributeManager.get_choice_values(attr.id, value_type, attr.choice_web_hook)
|
||||
if str(value) not in list(map(str, [i[0] for i in choice_values])):
|
||||
return abort(400, ErrFormat.not_in_choice_values.format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_unique(value_table, attr, ci_id, value):
|
||||
existed = db.session.query(value_table.attr_id).filter(
|
||||
def __check_is_unique(value_table, attr, ci_id, type_id, value):
|
||||
existed = db.session.query(value_table.attr_id).join(CI, CI.id == value_table.ci_id).filter(
|
||||
CI.type_id == type_id).filter(
|
||||
value_table.attr_id == attr.id).filter(value_table.deleted.is_(False)).filter(
|
||||
value_table.value == value).filter(value_table.ci_id != ci_id).first()
|
||||
existed and abort(400, "attribute <{0}> value {1} must be unique".format(attr.alias, value))
|
||||
|
||||
existed and abort(400, ErrFormat.attribute_value_unique_required.format(attr.alias, value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_required(type_id, attr, value):
|
||||
type_attr = CITypeAttributeCache.get(type_id, attr.id)
|
||||
def __check_is_required(type_id, attr, value, type_attr=None):
|
||||
type_attr = type_attr or CITypeAttributeCache.get(type_id, attr.id)
|
||||
if type_attr and type_attr.is_required and not value and value != 0:
|
||||
return abort(400, "attribute <{0}> value is required".format(attr.alias))
|
||||
return abort(400, ErrFormat.attribute_value_required.format(attr.alias))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci):
|
||||
def _validate(self, attr, value, value_table, ci=None, type_id=None, ci_id=None, type_attr=None):
|
||||
ci = ci or {}
|
||||
v = self.__deserialize_value(attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self.__check_is_choice(attr, attr.value_type, v)
|
||||
attr.is_unique and self.__check_is_unique(value_table, attr, ci.id, v)
|
||||
attr.is_unique and self.__check_is_unique(
|
||||
value_table, attr, ci and ci.id or ci_id, ci and ci.type_id or type_id, v)
|
||||
|
||||
self.__check_is_required(ci.type_id, attr, v)
|
||||
self.__check_is_required(ci and ci.type_id or type_id, attr, v, type_attr=type_attr)
|
||||
|
||||
if v == "" and attr.value_type not in (ValueTypeEnum.TEXT,):
|
||||
v = None
|
||||
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
def _write_change(ci_id, attr_id, operate_type, old, new):
|
||||
AttributeHistoryManger.add(ci_id, [(attr_id, operate_type, old, new)])
|
||||
def _write_change(ci_id, attr_id, operate_type, old, new, record_id, type_id):
|
||||
return AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id)
|
||||
|
||||
def create_or_update_attr_value(self, key, value, ci, _no_attribute_policy=ExistPolicy.IGNORE):
|
||||
@staticmethod
|
||||
def _write_change2(changed):
|
||||
record_id = None
|
||||
for ci_id, attr_id, operate_type, old, new, type_id in changed:
|
||||
record_id = AttributeHistoryManger.add(record_id, ci_id, [(attr_id, operate_type, old, new)], type_id,
|
||||
commit=False, flush=False)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
current_app.logger.error("write change failed: {}".format(str(e)))
|
||||
|
||||
return record_id
|
||||
|
||||
@staticmethod
|
||||
def __compute_attr_value_from_expr(expr, ci_dict):
|
||||
t = jinja2.Template(expr).render(ci_dict)
|
||||
|
||||
try:
|
||||
return eval(t)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return t
|
||||
|
||||
@staticmethod
|
||||
def __compute_attr_value_from_script(script, ci_dict):
|
||||
script = jinja2.Template(script).render(ci_dict)
|
||||
|
||||
script_f = tempfile.NamedTemporaryFile(delete=False, suffix=".py")
|
||||
script_f.write(script.encode('utf-8'))
|
||||
script_f.close()
|
||||
|
||||
try:
|
||||
path = script_f.name
|
||||
dir_name, name = os.path.dirname(path), os.path.basename(path)[:-3]
|
||||
|
||||
fp, path, desc = imp.find_module(name, [dir_name])
|
||||
|
||||
mod = imp.load_module(name, fp, path, desc)
|
||||
|
||||
if hasattr(mod, 'computed'):
|
||||
return mod.computed()
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
|
||||
finally:
|
||||
os.remove(script_f.name)
|
||||
|
||||
@staticmethod
|
||||
def _jinja2_parse(content):
|
||||
schema = to_json_schema(infer(content))
|
||||
|
||||
return [var for var in schema.get("properties")]
|
||||
|
||||
def _compute_attr_value(self, attr, payload, ci):
|
||||
attrs = self._jinja2_parse(attr['compute_expr']) if attr.get('compute_expr') else \
|
||||
self._jinja2_parse(attr['compute_script'])
|
||||
not_existed = [i for i in attrs if i not in payload]
|
||||
if ci is not None:
|
||||
payload.update(self.get_attr_values(not_existed, ci.id))
|
||||
|
||||
if attr['compute_expr']:
|
||||
return self.__compute_attr_value_from_expr(attr['compute_expr'], payload)
|
||||
elif attr['compute_script']:
|
||||
return self.__compute_attr_value_from_script(attr['compute_script'], payload)
|
||||
|
||||
def handle_ci_compute_attributes(self, ci_dict, computed_attrs, ci):
|
||||
payload = copy.deepcopy(ci_dict)
|
||||
for attr in computed_attrs:
|
||||
computed_value = self._compute_attr_value(attr, payload, ci)
|
||||
if computed_value is not None:
|
||||
ci_dict[attr['name']] = computed_value
|
||||
|
||||
def valid_attr_value(self, ci_dict, type_id, ci_id, name2attr, alias2attr=None, ci_attr2type_attr=None):
|
||||
key2attr = dict()
|
||||
alias2attr = alias2attr or {}
|
||||
ci_attr2type_attr = ci_attr2type_attr or {}
|
||||
|
||||
for key, value in ci_dict.items():
|
||||
attr = name2attr.get(key) or alias2attr.get(key)
|
||||
key2attr[key] = attr
|
||||
|
||||
value_table = TableMap(attr=attr).table
|
||||
|
||||
try:
|
||||
if attr.is_list:
|
||||
value_list = [self._validate(attr, i, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||
type_attr=ci_attr2type_attr.get(attr.id))
|
||||
for i in handle_arg_list(value)]
|
||||
ci_dict[key] = value_list
|
||||
if not value_list:
|
||||
self.__check_is_required(type_id, attr, '')
|
||||
|
||||
else:
|
||||
value = self._validate(attr, value, value_table, ci=None, type_id=type_id, ci_id=ci_id,
|
||||
type_attr=ci_attr2type_attr.get(attr.id))
|
||||
ci_dict[key] = value
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format(
|
||||
"{}({})".format(attr.alias, attr.name), value))
|
||||
|
||||
return key2attr
|
||||
|
||||
def create_or_update_attr_value2(self, ci, ci_dict, key2attr):
|
||||
"""
|
||||
add or update attribute value, then write history
|
||||
:param ci: instance object
|
||||
:param ci_dict: attribute dict
|
||||
:param key2attr: attr key to attr
|
||||
:return:
|
||||
"""
|
||||
changed = []
|
||||
for key, value in ci_dict.items():
|
||||
attr = key2attr.get(key)
|
||||
if not attr:
|
||||
continue # not be here
|
||||
value_table = TableMap(attr=attr).table
|
||||
|
||||
if attr.is_list:
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value) - 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))
|
||||
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
changed.append((ci.id, attr.id, OperateType.DELETE, v, None, ci.type_id))
|
||||
else:
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.ADD, None, value, ci.type_id))
|
||||
else:
|
||||
if existed_value != value:
|
||||
if value is None:
|
||||
existed_attr.delete(flush=False, commit=False)
|
||||
else:
|
||||
existed_attr.update(value=value, flush=False, commit=False)
|
||||
|
||||
changed.append((ci.id, attr.id, OperateType.UPDATE, existed_value, value, ci.type_id))
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.attribute_value_unknown_error.format(str(e)))
|
||||
|
||||
return self._write_change2(changed)
|
||||
|
||||
def create_or_update_attr_value(self, key, value, ci, _no_attribute_policy=ExistPolicy.IGNORE, record_id=None):
|
||||
"""
|
||||
add or update attribute value, then write history
|
||||
:param key: id, name or alias
|
||||
:param value:
|
||||
:param ci: instance object
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param record_id: op record
|
||||
:return:
|
||||
"""
|
||||
attr = self._get_attr(key)
|
||||
@@ -125,53 +302,55 @@ class AttributeValueManager(object):
|
||||
if _no_attribute_policy == ExistPolicy.IGNORE:
|
||||
return
|
||||
if _no_attribute_policy == ExistPolicy.REJECT:
|
||||
return abort(400, 'attribute {0} does not exist'.format(key))
|
||||
return abort(400, ErrFormat.attribute_not_found.format(key))
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
value_table = TableMap(attr=attr).table
|
||||
|
||||
if attr.is_list:
|
||||
value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)]
|
||||
if not value_list:
|
||||
self.__check_is_required(ci.type_id, attr, '')
|
||||
try:
|
||||
if attr.is_list:
|
||||
value_list = [self._validate(attr, i, value_table, ci) for i in handle_arg_list(value)]
|
||||
if not value_list:
|
||||
self.__check_is_required(ci.type_id, attr, '')
|
||||
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id,
|
||||
ci_id=ci.id,
|
||||
to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value_list) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value_list)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v)
|
||||
self._write_change(ci.id, attr.id, OperateType.ADD, None, v)
|
||||
existed_attrs = value_table.get_by(attr_id=attr.id, ci_id=ci.id, to_dict=False)
|
||||
existed_values = [i.value for i in existed_attrs]
|
||||
added = set(value_list) - set(existed_values)
|
||||
deleted = set(existed_values) - set(value_list)
|
||||
for v in added:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=v)
|
||||
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, v, record_id, ci.type_id)
|
||||
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
existed_attr.delete()
|
||||
self._write_change(ci.id, attr.id, OperateType.DELETE, v, None)
|
||||
else:
|
||||
value = self._validate(attr, value, value_table, ci)
|
||||
existed_attr = value_table.get_by(attr_id=attr.id,
|
||||
ci_id=ci.id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
if existed_value is None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value)
|
||||
|
||||
self._write_change(ci.id, attr.id, OperateType.ADD, None, value)
|
||||
for v in deleted:
|
||||
existed_attr = existed_attrs[existed_values.index(v)]
|
||||
existed_attr.delete()
|
||||
record_id = self._write_change(ci.id, attr.id, OperateType.DELETE, v, None, record_id, ci.type_id)
|
||||
else:
|
||||
if existed_value != value:
|
||||
if value != 0 and not value and attr.value_type != ValueTypeEnum.TEXT:
|
||||
existed_attr.delete()
|
||||
else:
|
||||
existed_attr.update(value=value)
|
||||
value = self._validate(attr, value, value_table, ci)
|
||||
existed_attr = value_table.get_by(attr_id=attr.id, ci_id=ci.id, first=True, to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
if existed_value is None and value is not None:
|
||||
value_table.create(ci_id=ci.id, attr_id=attr.id, value=value)
|
||||
|
||||
self._write_change(ci.id, attr.id, OperateType.UPDATE, existed_value, value)
|
||||
record_id = self._write_change(ci.id, attr.id, OperateType.ADD, None, value, record_id, ci.type_id)
|
||||
else:
|
||||
if existed_value != value:
|
||||
if value is None:
|
||||
existed_attr.delete()
|
||||
else:
|
||||
existed_attr.update(value=value)
|
||||
|
||||
record_id = self._write_change(ci.id, attr.id, OperateType.UPDATE,
|
||||
existed_value, value, record_id, ci.type_id)
|
||||
|
||||
return record_id
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
return abort(400, ErrFormat.attribute_value_invalid2.format("{}({})".format(attr.alias, attr.name), value))
|
||||
|
||||
@staticmethod
|
||||
def delete_attr_value(attr_id, ci_id):
|
||||
attr = AttributeCache.get(attr_id)
|
||||
if attr is not None:
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
value_table = TableMap(attr=attr).table
|
||||
for item in value_table.get_by(attr_id=attr.id, ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
|
Reference in New Issue
Block a user