150 lines
5.7 KiB
Python
150 lines
5.7 KiB
Python
#!/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() |