commit aebada289452ace556f3246243c4a67c3ec66430 Author: 蓝仔欣 <2775762652@qq.com> Date: Thu Sep 11 16:31:32 2025 +0800 first commit diff --git a/sync.py b/sync.py new file mode 100644 index 0000000..03191e1 --- /dev/null +++ b/sync.py @@ -0,0 +1,150 @@ +#!/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() \ No newline at end of file