Files
asset_sync/sync.py
2025-09-11 16:31:32 +08:00

150 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from pyVmomi import vim
from pyVim.connect import SmartConnect, Disconnect
import ssl
import argparse
import getpass
import atexit
import requests
import os
import sys
import ipaddress
def get_vm_info(content):
vm_view = content.viewManager.CreateContainerView(
content.rootFolder, [vim.VirtualMachine], True)
return [
{
'name': vm.name,
'ip': vm.guest.ipAddress if vm.guest and vm.guest.ipAddress else "N/A"
}
for vm in vm_view.view
]
def get_session(acl_url, username, password):
login_url = f"{acl_url}/acl/login"
session = requests.Session()
resp = session.post(login_url, json={
"username": username,
"password": password
})
if resp.status_code != 200:
raise Exception(f"登录失败,状态码: {resp.status_code},响应: {resp.text}")
print("🔐 登录成功")
return session
def main():
parser = argparse.ArgumentParser(description='同步 vCenter 虚拟机资产信息到 OneTerm')
parser.add_argument('--vc-host', required=True, help='vCenter服务器地址')
parser.add_argument('--vc-user', required=True, help='vCenter用户名')
parser.add_argument('--vc-pass', help='vCenter密码可选优先级高于环境变量')
parser.add_argument('--api-url', required=True, help='OneTerm API 地址(如 http://host:port/api')
parser.add_argument('--acl-url', required=True, help='OneTerm ACL 地址(如 http://host:port/api')
parser.add_argument('--api-user', default=os.environ.get("API_USERNAME", "admin"), help='API 登录用户名')
parser.add_argument('--api-pass', default=os.environ.get("API_PASSWORD", "Duguang.io123"), help='API 登录密码')
args = parser.parse_args()
# 获取 vCenter 密码
password = args.vc_pass or os.environ.get("VCENTER_PASSWORD")
if not password:
password = getpass.getpass(f"请输入 {args.vc_user} 的密码: ")
try:
# 创建不验证SSL的上下文
context = ssl._create_unverified_context()
# 连接 vCenter
si = SmartConnect(
host=args.vc_host,
user=args.vc_user,
pwd=password,
sslContext=context
)
atexit.register(Disconnect, si)
# 获取虚拟机信息
vms = get_vm_info(si.RetrieveContent())
vm_dict = {}
for vm in vms:
name = vm["name"]
ip = vm["ip"]
try:
ipaddress.ip_address(ip)
vm_dict[name] = ip
except ValueError:
print(f"⚠️ 忽略资产IP 非法): {name} -> {ip}")
# 登录获取 token
session = get_session(args.acl_url, args.api_user, args.api_pass)
# 获取已有资产
asset_url = f"{args.api_url}/oneterm/v1/asset?page_index=1&page_size=100"
resp = session.get(asset_url)
if resp.status_code != 200:
raise Exception(f"获取资产列表失败,状态码: {resp.status_code},响应: {resp.text}")
# 根据实际接口返回格式提取资产
data = resp.json()
items = data.get("data", {}).get("list", []) or data.get("data", {}).get("items", [])
exist_names = {item["name"]: item for item in items}
# 创建新资产
for name, ip in vm_dict.items():
if name not in exist_names:
try:
ipaddress.ip_address(ip)
except ValueError:
print(f"⚠️ 忽略资产IP 非法): {name} -> {ip}")
continue
print(f"🆕 创建资产: {name} -> {ip}")
create_resp = session.post(f"{args.api_url}/oneterm/v1/asset", json={
"name": name,
"ip": ip,
"parent_id": 1,
"allow": True,
"cmd_ids": "",
"connectable": True,
"protocols": ["ssh:22"],
"authorization": {"1": [1]},
"comment": ""
})
if create_resp.status_code >= 300:
print(f"⚠️ 创建失败: {create_resp.status_code} - {create_resp.text}")
if create_resp.status_code >= 300:
print(f"⚠️ 创建失败: {create_resp.status_code} - {create_resp.text}")
# 删除旧资产
# 重新拉取最新资产(包含新添加的),确保删除所有离线资产
asset_url = f"{args.api_url}/oneterm/v1/asset?page_index=1&page_size=100"
latest_resp = session.get(asset_url)
if latest_resp.status_code != 200:
raise Exception(f"重新获取资产失败: {latest_resp.status_code} - {latest_resp.text}")
latest_data = latest_resp.json()
latest_items = latest_data.get("data", {}).get("list", []) or latest_data.get("data", {}).get("items", [])
for item in latest_items:
ip_in_oneterm = str(item.get("ip", "")).strip().lower()
asset_id = item["id"]
# 判断并删除离线状态的资产
if item.get("connectable") is False:
delete_url = f"{args.api_url}/oneterm/v1/asset/{asset_id}"
print(f"❌ 删除资产: {ip_in_oneterm} -> ID: {asset_id}")
del_resp = session.delete(delete_url)
if del_resp.status_code >= 300:
print(f"⚠️ 删除失败: {del_resp.status_code} - {del_resp.text}")
else:
print(f"✅ 删除成功: {ip_in_oneterm}")
print("✅ 同步完成")
except Exception as e:
print(f"❌ 错误: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()