#!/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()