mirror of https://github.com/veops/cmdb.git
feat(api): auto discovery supports mapping (#569)
This commit is contained in:
parent
b22b8b286b
commit
d5db68d7d0
24
README.md
24
README.md
|
@ -73,7 +73,8 @@
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
### Docker 一键快速构建
|
### Docker 一键快速构建
|
||||||
> 方法一
|
|
||||||
|
[//]: # (> 方法一)
|
||||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
||||||
- 第二步: 拷贝项目
|
- 第二步: 拷贝项目
|
||||||
```shell
|
```shell
|
||||||
|
@ -83,13 +84,20 @@ git clone https://github.com/veops/cmdb.git
|
||||||
```
|
```
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
> 方法二, 该方法适用于linux系统
|
|
||||||
- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2)
|
[//]: # (> 方法二, 该方法适用于linux系统)
|
||||||
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
|
|
||||||
```shell
|
[//]: # (- 第一步: 先安装 Docker 环境, 以及Docker Compose (v2))
|
||||||
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
|
|
||||||
sh install.sh install
|
[//]: # (- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`)
|
||||||
```
|
|
||||||
|
[//]: # (```shell)
|
||||||
|
|
||||||
|
[//]: # (curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh)
|
||||||
|
|
||||||
|
[//]: # (sh install.sh install)
|
||||||
|
|
||||||
|
[//]: # (```)
|
||||||
|
|
||||||
### [本地开发环境搭建](docs/local.md)
|
### [本地开发环境搭建](docs/local.md)
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ colorama = ">=0.4.6"
|
||||||
pycryptodomex = ">=3.19.0"
|
pycryptodomex = ">=3.19.0"
|
||||||
lz4 = ">=4.3.2"
|
lz4 = ">=4.3.2"
|
||||||
python-magic = "==0.4.27"
|
python-magic = "==0.4.27"
|
||||||
|
jsonpath = "==0.82.2"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
# Testing
|
# Testing
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import jsonpath
|
||||||
import os
|
import os
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
@ -9,10 +10,11 @@ from flask_login import current_user
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from api.extensions import db
|
from api.extensions import db
|
||||||
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
|
from api.lib.cmdb.auto_discovery.const import CLOUD_MAP
|
||||||
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
|
from api.lib.cmdb.auto_discovery.const import DEFAULT_INNER
|
||||||
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
|
from api.lib.cmdb.auto_discovery.const import PRIVILEGED_USERS
|
||||||
from api.lib.cmdb.cache import AttributeCache
|
from api.lib.cmdb.cache import AttributeCache
|
||||||
|
from api.lib.cmdb.cache import AutoDiscoveryMappingCache
|
||||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||||
from api.lib.cmdb.cache import CITypeCache
|
from api.lib.cmdb.cache import CITypeCache
|
||||||
from api.lib.cmdb.ci import CIManager
|
from api.lib.cmdb.ci import CIManager
|
||||||
|
@ -279,7 +281,6 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||||
|
|
||||||
result.append(rule)
|
result.append(rule)
|
||||||
|
|
||||||
|
|
||||||
ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or ""
|
ad_rules_updated_at = (SystemConfigManager.get('ad_rules_updated_at') or {}).get('option', {}).get('v') or ""
|
||||||
new_last_update_at = ""
|
new_last_update_at = ""
|
||||||
for i in result:
|
for i in result:
|
||||||
|
@ -361,10 +362,11 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||||
en_name = i['en']
|
en_name = i['en']
|
||||||
break
|
break
|
||||||
if en_name and kwargs['extra_option'].get('category'):
|
if en_name and kwargs['extra_option'].get('category'):
|
||||||
for item in ClOUD_MAP[en_name]:
|
for item in CLOUD_MAP[en_name]:
|
||||||
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
||||||
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
||||||
kwargs['extra_option']['category']]
|
kwargs['extra_option']['category']]
|
||||||
|
kwargs["extra_option"]["provider"] = en_name
|
||||||
break
|
break
|
||||||
|
|
||||||
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
|
||||||
|
@ -399,10 +401,11 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
|
||||||
en_name = i['en']
|
en_name = i['en']
|
||||||
break
|
break
|
||||||
if en_name and kwargs['extra_option'].get('category'):
|
if en_name and kwargs['extra_option'].get('category'):
|
||||||
for item in ClOUD_MAP[en_name]:
|
for item in CLOUD_MAP[en_name]:
|
||||||
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
if item["collect_key_map"].get(kwargs['extra_option']['category']):
|
||||||
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
kwargs["extra_option"]["collect_key"] = item["collect_key_map"][
|
||||||
kwargs['extra_option']['category']]
|
kwargs['extra_option']['category']]
|
||||||
|
kwargs["extra_option"]["provider"] = en_name
|
||||||
break
|
break
|
||||||
|
|
||||||
if 'attributes' in kwargs:
|
if 'attributes' in kwargs:
|
||||||
|
@ -479,6 +482,7 @@ class AutoDiscoveryCITypeRelationCRUD(DBMixin):
|
||||||
def get_all(cls, type_ids=None):
|
def get_all(cls, type_ids=None):
|
||||||
res = cls.cls.get_by(to_dict=False)
|
res = cls.cls.get_by(to_dict=False)
|
||||||
return [i for i in res if type_ids is None or i.ad_type_id in type_ids]
|
return [i for i in res if type_ids is None or i.ad_type_id in type_ids]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_type_id(cls, type_id, to_dict=False):
|
def get_by_type_id(cls, type_id, to_dict=False):
|
||||||
return cls.cls.get_by(ad_type_id=type_id, to_dict=to_dict)
|
return cls.cls.get_by(ad_type_id=type_id, to_dict=to_dict)
|
||||||
|
@ -543,7 +547,6 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||||
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
adts = AutoDiscoveryCITypeCRUD.get_by_type_id(type_id)
|
||||||
for adt in adts:
|
for adt in adts:
|
||||||
attr_names |= set((adt.attributes or {}).values())
|
attr_names |= set((adt.attributes or {}).values())
|
||||||
|
|
||||||
return [attr for attr in attributes if attr['name'] in attr_names]
|
return [attr for attr in attributes if attr['name'] in attr_names]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -674,7 +677,16 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||||
|
|
||||||
ci_id = None
|
ci_id = None
|
||||||
if adt.attributes:
|
if adt.attributes:
|
||||||
ci_dict = {adt.attributes[k]: v for k, v in adc.instance.items() if k in adt.attributes}
|
ci_dict = {adt.attributes[k]: None if not v and isinstance(v, (list, dict)) else v
|
||||||
|
for k, v in adc.instance.items() if k in adt.attributes}
|
||||||
|
extra_option = adt.extra_option or {}
|
||||||
|
mapping, path_mapping = AutoDiscoveryHTTPManager.get_predefined_value_mapping(
|
||||||
|
extra_option.get('provider'), extra_option.get('category'))
|
||||||
|
if mapping:
|
||||||
|
ci_dict = {k: (mapping.get(k) or {}).get(str(v), v) for k, v in ci_dict.items()}
|
||||||
|
if path_mapping:
|
||||||
|
ci_dict = {k: jsonpath.jsonpath(v, path_mapping[k]) if k in path_mapping else v
|
||||||
|
for k, v in ci_dict.items()}
|
||||||
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, _is_admin=True, **ci_dict)
|
ci_id = CIManager.add(adc.type_id, is_auto_discovery=True, _is_admin=True, **ci_dict)
|
||||||
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
|
AutoDiscoveryExecHistoryCRUD().add(type_id=adt.type_id,
|
||||||
stdout="accept resource: {}".format(adc.unique_value))
|
stdout="accept resource: {}".format(adc.unique_value))
|
||||||
|
@ -715,7 +727,7 @@ class AutoDiscoveryCICRUD(DBMixin):
|
||||||
class AutoDiscoveryHTTPManager(object):
|
class AutoDiscoveryHTTPManager(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_categories(name):
|
def get_categories(name):
|
||||||
categories = (ClOUD_MAP.get(name) or {}) or []
|
categories = (CLOUD_MAP.get(name) or {}) or []
|
||||||
for item in copy.deepcopy(categories):
|
for item in copy.deepcopy(categories):
|
||||||
item.pop('map', None)
|
item.pop('map', None)
|
||||||
item.pop('collect_key_map', None)
|
item.pop('collect_key_map', None)
|
||||||
|
@ -738,16 +750,52 @@ class AutoDiscoveryHTTPManager(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes(provider, resource):
|
def get_attributes(provider, resource):
|
||||||
for item in (ClOUD_MAP.get(provider) or {}):
|
for item in (CLOUD_MAP.get(provider) or {}):
|
||||||
for _resource in (item.get('map') or {}):
|
for _resource in (item.get('map') or {}):
|
||||||
if _resource == resource:
|
if _resource == resource:
|
||||||
tpt = item['map'][_resource]
|
tpt = item['map'][_resource]
|
||||||
|
if isinstance(tpt, dict):
|
||||||
|
tpt = tpt.get('template')
|
||||||
if tpt and os.path.exists(os.path.join(PWD, tpt)):
|
if tpt and os.path.exists(os.path.join(PWD, tpt)):
|
||||||
with open(os.path.join(PWD, tpt)) as f:
|
with open(os.path.join(PWD, tpt)) as f:
|
||||||
return json.loads(f.read())
|
return json.loads(f.read())
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_mapping(provider, resource):
|
||||||
|
for item in (CLOUD_MAP.get(provider) or {}):
|
||||||
|
for _resource in (item.get('map') or {}):
|
||||||
|
if _resource == resource:
|
||||||
|
mapping = item['map'][_resource]
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return {}
|
||||||
|
name = mapping.get('mapping')
|
||||||
|
mapping = AutoDiscoveryMappingCache.get(name)
|
||||||
|
if isinstance(mapping, dict):
|
||||||
|
return {mapping[key][provider]['key'].split('.')[0]: key for key in mapping if
|
||||||
|
mapping[key].get(provider, {}).get('key')}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_predefined_value_mapping(provider, resource):
|
||||||
|
for item in (CLOUD_MAP.get(provider) or {}):
|
||||||
|
for _resource in (item.get('map') or {}):
|
||||||
|
if _resource == resource:
|
||||||
|
mapping = item['map'][_resource]
|
||||||
|
if not isinstance(mapping, dict):
|
||||||
|
return {}, {}
|
||||||
|
name = mapping.get('mapping')
|
||||||
|
mapping = AutoDiscoveryMappingCache.get(name)
|
||||||
|
if isinstance(mapping, dict):
|
||||||
|
return ({key: mapping[key][provider].get('map') for key in mapping if
|
||||||
|
mapping[key].get(provider, {}).get('map')},
|
||||||
|
{key: mapping[key][provider]['key'].split('.', 1)[1] for key in mapping if
|
||||||
|
(mapping[key].get(provider, {}).get('key') or '').split('.')[1:]})
|
||||||
|
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
|
|
||||||
class AutoDiscoverySNMPManager(object):
|
class AutoDiscoverySNMPManager(object):
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ DEFAULT_INNER = [
|
||||||
dict(name="华为云", en="huaweicloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
dict(name="华为云", en="huaweicloud", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||||
option={'icon': {'name': 'caise-huaweiyun'}, "en": "huaweicloud"}),
|
option={'icon': {'name': 'caise-huaweiyun'}, "en": "huaweicloud"}),
|
||||||
dict(name="AWS", en="aws", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
dict(name="AWS", en="aws", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||||
option={'icon': {'name': 'caise-aws'}}),
|
option={'icon': {'name': 'caise-aws'}, "en": "aws"}),
|
||||||
|
|
||||||
dict(name="VCenter", en="vcenter", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
dict(name="VCenter", en="vcenter", type=AutoDiscoveryType.HTTP, is_inner=True, is_plugin=False,
|
||||||
option={'icon': {'name': 'cmdb-vcenter'}, "category": "private_cloud", "en": "vcenter"}),
|
option={'icon': {'name': 'cmdb-vcenter'}, "category": "private_cloud", "en": "vcenter"}),
|
||||||
|
@ -34,14 +34,14 @@ DEFAULT_INNER = [
|
||||||
option={'icon': {'name': 'caise-dayinji'}}),
|
option={'icon': {'name': 'caise-dayinji'}}),
|
||||||
]
|
]
|
||||||
|
|
||||||
ClOUD_MAP = {
|
CLOUD_MAP = {
|
||||||
"aliyun": [
|
"aliyun": [
|
||||||
{
|
{
|
||||||
"category": "计算",
|
"category": "计算",
|
||||||
"items": ["云服务器 ECS", "云服务器 Disk"],
|
"items": ["云服务器 ECS", "云服务器 Disk"],
|
||||||
"map": {
|
"map": {
|
||||||
"云服务器 ECS": "templates/aliyun_ecs.json",
|
"云服务器 ECS": {"template": "templates/aliyun_ecs.json", "mapping": "ecs"},
|
||||||
"云服务器 Disk": "templates/aliyun_ecs_disk2.json",
|
"云服务器 Disk": {"template": "templates/aliyun_ecs_disk2.json", "mapping": "evs"},
|
||||||
},
|
},
|
||||||
"collect_key_map": {
|
"collect_key_map": {
|
||||||
"云服务器 ECS": "ali.ecs",
|
"云服务器 ECS": "ali.ecs",
|
||||||
|
@ -57,10 +57,10 @@ ClOUD_MAP = {
|
||||||
"交换机Switch",
|
"交换机Switch",
|
||||||
],
|
],
|
||||||
"map": {
|
"map": {
|
||||||
"内容分发CDN": "templates/aliyun_cdn.json",
|
"内容分发CDN": {"template": "templates/aliyun_cdn.json", "mapping": "CDN"},
|
||||||
"负载均衡SLB": "templates/aliyun_slb.json",
|
"负载均衡SLB": {"template": "templates/aliyun_slb.json", "mapping": "loadbalancer"},
|
||||||
"专有网络VPC": "templates/aliyun_vpc.json",
|
"专有网络VPC": {"template": "templates/aliyun_vpc.json", "mapping": "vpc"},
|
||||||
"交换机Switch": "templates/aliyun_switch.json",
|
"交换机Switch": {"template": "templates/aliyun_switch.json", "mapping": "vswitch"},
|
||||||
},
|
},
|
||||||
"collect_key_map": {
|
"collect_key_map": {
|
||||||
"内容分发CDN": "ali.cdn",
|
"内容分发CDN": "ali.cdn",
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
|
@ -546,3 +548,20 @@ class CMDBCounterCache(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_sub_counter(cls):
|
def get_sub_counter(cls):
|
||||||
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
return cache.get(cls.KEY3) or cls.flush_sub_counter()
|
||||||
|
|
||||||
|
|
||||||
|
class AutoDiscoveryMappingCache(object):
|
||||||
|
PREFIX = 'CMDB::AutoDiscovery::Mapping::{}'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, name):
|
||||||
|
res = cache.get(cls.PREFIX.format(name)) or {}
|
||||||
|
if not res:
|
||||||
|
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "auto_discovery/mapping/{}.yaml".format(name))
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
mapping = yaml.safe_load(f)
|
||||||
|
res = mapping.get('mapping') or {}
|
||||||
|
res and cache.set(cls.PREFIX.format(name), res, timeout=0)
|
||||||
|
|
||||||
|
return res
|
|
@ -29,12 +29,21 @@ def string2int(x):
|
||||||
|
|
||||||
|
|
||||||
def str2datetime(x):
|
def str2datetime(x):
|
||||||
|
|
||||||
|
x = x.replace('T', ' ')
|
||||||
|
x = x.replace('Z', '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(x, "%Y-%m-%d").date()
|
return datetime.datetime.strptime(x, "%Y-%m-%d").date()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
|
try:
|
||||||
|
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S").date()
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
|
||||||
class ValueTypeMap(object):
|
class ValueTypeMap(object):
|
||||||
|
|
|
@ -111,6 +111,7 @@ class AutoDiscoveryRuleTemplateFileView(APIView):
|
||||||
class AutoDiscoveryRuleHTTPView(APIView):
|
class AutoDiscoveryRuleHTTPView(APIView):
|
||||||
url_prefix = ("/adr/http/<string:name>/categories",
|
url_prefix = ("/adr/http/<string:name>/categories",
|
||||||
"/adr/http/<string:name>/attributes",
|
"/adr/http/<string:name>/attributes",
|
||||||
|
"/adr/http/<string:name>/mapping",
|
||||||
"/adr/snmp/<string:name>/attributes",
|
"/adr/snmp/<string:name>/attributes",
|
||||||
"/adr/components/<string:name>/attributes",)
|
"/adr/components/<string:name>/attributes",)
|
||||||
|
|
||||||
|
@ -125,6 +126,10 @@ class AutoDiscoveryRuleHTTPView(APIView):
|
||||||
resource = request.values.get('resource')
|
resource = request.values.get('resource')
|
||||||
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, resource))
|
return self.jsonify(AutoDiscoveryHTTPManager.get_attributes(name, resource))
|
||||||
|
|
||||||
|
if "mapping" in request.url:
|
||||||
|
resource = request.values.get('resource')
|
||||||
|
return self.jsonify(AutoDiscoveryHTTPManager.get_mapping(name, resource))
|
||||||
|
|
||||||
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
|
return self.jsonify(AutoDiscoveryHTTPManager.get_categories(name))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -55,3 +55,4 @@ pycryptodomex>=3.19.0
|
||||||
colorama>=0.4.6
|
colorama>=0.4.6
|
||||||
lz4>=4.3.2
|
lz4>=4.3.2
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
|
jsonpath==0.82.2
|
||||||
|
|
Loading…
Reference in New Issue