|
#!/usr/bin/env python3 |
|
|
|
import os |
|
import json |
|
import requests |
|
import urllib3 |
|
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
|
|
|
# === НАСТРОЙКИ === |
|
ZABBIX_SRC_URL = "https://OLD_ZABBIX/api_jsonrpc.php" |
|
ZABBIX_DST_URL = "https://NEW_ZABBIX/api_jsonrpc.php" |
|
ZABBIX_SRC_USER = "Admin" |
|
ZABBIX_SRC_PASS = "zabbix" |
|
ZABBIX_DST_USER = "Admin" |
|
ZABBIX_DST_PASS = "zabbix" |
|
VERIFY_SSL = False |
|
|
|
# ── helpers ─────────────────────────────────────────────────────────────────── |
|
|
|
def make_session(): |
|
s = requests.Session() |
|
s.verify = VERIFY_SSL |
|
return s |
|
|
|
def api_call(session, url, method, params, auth=None): |
|
headers = {"Content-Type": "application/json"} |
|
if auth: |
|
headers["Authorization"] = f"Bearer {auth}" |
|
|
|
resp = session.post(url, json={ |
|
"jsonrpc": "2.0", |
|
"method": method, |
|
"params": params, |
|
"id": 1, |
|
}, headers=headers) |
|
resp.raise_for_status() |
|
|
|
data = resp.json() |
|
if "error" in data: |
|
raise RuntimeError(f"API error [{method}]: {data['error']}") |
|
return data["result"] |
|
|
|
def login(session, url, user, password): |
|
try: |
|
return api_call(session, url, "user.login", |
|
{"username": user, "password": password}) |
|
except RuntimeError: |
|
return api_call(session, url, "user.login", |
|
{"user": user, "password": password}) |
|
|
|
def logout(session, url, token): |
|
try: |
|
api_call(session, url, "user.logout", [], auth=token) |
|
except Exception: |
|
pass |
|
|
|
# ── main ────────────────────────────────────────────────────────────────────── |
|
|
|
src = make_session() |
|
dst = make_session() |
|
|
|
# 1. Авторизация |
|
print("Авторизация...") |
|
src_token = login(src, ZABBIX_SRC_URL, ZABBIX_SRC_USER, ZABBIX_SRC_PASS) |
|
print(f" ✓ Источник: {ZABBIX_SRC_URL}") |
|
dst_token = login(dst, ZABBIX_DST_URL, ZABBIX_DST_USER, ZABBIX_DST_PASS) |
|
print(f" ✓ Назначение: {ZABBIX_DST_URL}\n") |
|
|
|
# 2. Получить все host groups с источника и создать маппинг name→id на dst |
|
print("Синхронизирую host groups...") |
|
src_groups = api_call(src, ZABBIX_SRC_URL, "hostgroup.get", |
|
{"output": ["groupid", "name"]}, auth=src_token) |
|
|
|
dst_groups = api_call(dst, ZABBIX_DST_URL, "hostgroup.get", |
|
{"output": ["groupid", "name"]}, auth=dst_token) |
|
dst_group_map = {g["name"]: g["groupid"] for g in dst_groups} |
|
|
|
# Создаём недостающие группы |
|
for g in src_groups: |
|
if g["name"] not in dst_group_map: |
|
result = api_call(dst, ZABBIX_DST_URL, "hostgroup.create", |
|
{"name": g["name"]}, auth=dst_token) |
|
dst_group_map[g["name"]] = result["groupids"][0] |
|
print(f" + Группа создана: {g['name']}") |
|
|
|
# Маппинг src groupid → dst groupid |
|
src_group_map = {g["groupid"]: dst_group_map[g["name"]] for g in src_groups} |
|
print(f" ✓ Групп всего: {len(src_groups)}\n") |
|
|
|
# 3. Получить все шаблоны на dst — для маппинга по имени |
|
print("Получаю шаблоны на назначении...") |
|
dst_templates = api_call(dst, ZABBIX_DST_URL, "template.get", |
|
{"output": ["templateid", "name"]}, auth=dst_token) |
|
dst_tpl_map = {t["name"]: t["templateid"] for t in dst_templates} |
|
print(f" ✓ Шаблонов на dst: {len(dst_templates)}\n") |
|
|
|
# 4. Получить все хосты с источника со всеми нужными полями |
|
print("Получаю хосты с источника...") |
|
src_hosts = api_call(src, ZABBIX_SRC_URL, "host.get", { |
|
"output": [ |
|
"hostid", "host", "name", "status", |
|
"description", "ipmi_authtype", "ipmi_privilege", |
|
"ipmi_username", "ipmi_password", |
|
"tls_connect", "tls_accept", |
|
"tls_issuer", "tls_subject", |
|
"tls_psk_identity", "tls_psk", |
|
], |
|
"selectGroups": ["groupid", "name"], |
|
"selectInterfaces": ["type", "main", "useip", "ip", "dns", "port", "details"], |
|
"selectTemplates": ["templateid", "name"], |
|
"selectMacros": ["macro", "value", "description", "type"], |
|
"selectTags": ["tag", "value"], |
|
"selectInventory": "extend", |
|
}, auth=src_token) |
|
print(f" ✓ Хостов найдено: {len(src_hosts)}\n") |
|
|
|
# 5. Получить уже существующие хосты на dst |
|
dst_hosts = api_call(dst, ZABBIX_DST_URL, "host.get", |
|
{"output": ["host"]}, auth=dst_token) |
|
dst_host_set = {h["host"] for h in dst_hosts} |
|
|
|
# 6. Создать хосты |
|
ok, skip, fail = 0, 0, 0 |
|
failed = [] |
|
|
|
print(f"{'─'*60}") |
|
for i, host in enumerate(src_hosts, start=1): |
|
hostname = host["host"] |
|
print(f"[{i}/{len(src_hosts)}] {hostname} ({host['name']})") |
|
|
|
if hostname in dst_host_set: |
|
print(f" ⚠ Уже существует, пропускаем") |
|
skip += 1 |
|
continue |
|
|
|
try: |
|
# Группы — переводим src groupid → dst groupid |
|
groups = [ |
|
{"groupid": src_group_map[g["groupid"]]} |
|
for g in host["groups"] |
|
if g["groupid"] in src_group_map |
|
] |
|
if not groups: |
|
raise ValueError("Нет подходящих групп на dst") |
|
|
|
# Шаблоны — ищем по имени на dst |
|
templates = [] |
|
for t in host.get("templates", []): |
|
if t["name"] in dst_tpl_map: |
|
templates.append({"templateid": dst_tpl_map[t["name"]]}) |
|
else: |
|
print(f" ⚠ Шаблон не найден на dst: {t['name']}") |
|
|
|
# Интерфейсы |
|
interfaces = [] |
|
for iface in host.get("interfaces", []): |
|
iface_obj = { |
|
"type": iface["type"], |
|
"main": iface["main"], |
|
"useip": iface["useip"], |
|
"ip": iface["ip"], |
|
"dns": iface["dns"], |
|
"port": iface["port"], |
|
} |
|
# SNMP details (type=2) |
|
if iface.get("details"): |
|
iface_obj["details"] = iface["details"] |
|
interfaces.append(iface_obj) |
|
|
|
# Инвентарь |
|
inventory_mode = -1 # disabled |
|
inventory = {} |
|
if host.get("inventory"): |
|
inventory_mode = 1 |
|
# убираем служебные поля |
|
skip_fields = {"hostid", "inventory_mode"} |
|
inventory = {k: v for k, v in host["inventory"].items() |
|
if k not in skip_fields and v} |
|
|
|
# TLS — если PSK но ключ пустой, сбрасываем на no encryption |
|
tls_connect = host.get("tls_connect", "1") |
|
tls_accept = host.get("tls_accept", "1") |
|
psk_identity = host.get("tls_psk_identity", "") |
|
psk = host.get("tls_psk", "") |
|
|
|
PSK_FLAG = 2 # bit/value для PSK в Zabbix |
|
if not psk_identity or not psk: |
|
# PSK данные недоступны — откатываемся на no encryption |
|
if str(tls_connect) == str(PSK_FLAG): |
|
tls_connect = "1" |
|
# tls_accept — битовая маска: убираем бит PSK (2) |
|
try: |
|
tls_accept_int = int(tls_accept) & ~PSK_FLAG |
|
tls_accept = str(tls_accept_int) if tls_accept_int > 0 else "1" |
|
except ValueError: |
|
tls_accept = "1" |
|
psk_identity = "" |
|
psk = "" |
|
|
|
params = { |
|
"host": hostname, |
|
"name": host["name"], |
|
"status": host["status"], |
|
"description": host.get("description", ""), |
|
"groups": groups, |
|
"interfaces": interfaces, |
|
"templates": templates, |
|
"macros": host.get("macros", []), |
|
"tags": host.get("tags", []), |
|
"inventory_mode": inventory_mode, |
|
"inventory": inventory, |
|
"tls_connect": tls_connect, |
|
"tls_accept": tls_accept, |
|
"tls_issuer": host.get("tls_issuer", ""), |
|
"tls_subject": host.get("tls_subject", ""), |
|
"ipmi_authtype": host.get("ipmi_authtype", -1), |
|
"ipmi_privilege": host.get("ipmi_privilege", 2), |
|
"ipmi_username": host.get("ipmi_username", ""), |
|
"ipmi_password": host.get("ipmi_password", ""), |
|
} |
|
|
|
# Добавляем PSK только если данные есть |
|
if psk_identity and psk: |
|
params["tls_psk_identity"] = psk_identity |
|
params["tls_psk"] = psk |
|
result = api_call(dst, ZABBIX_DST_URL, "host.create", |
|
params, auth=dst_token) |
|
print(f" ✓ Создан (id={result['hostids'][0]})") |
|
ok += 1 |
|
|
|
except (RuntimeError, ValueError) as e: |
|
print(f" ✗ Ошибка: {e}") |
|
failed.append((hostname, str(e))) |
|
fail += 1 |
|
|
|
# 7. Логаут |
|
logout(src, ZABBIX_SRC_URL, src_token) |
|
logout(dst, ZABBIX_DST_URL, dst_token) |
|
|
|
# 8. Итог |
|
print(f"\n{'='*60}") |
|
print(f" ✓ Создано: {ok}") |
|
print(f" ⚠ Пропущено: {skip}") |
|
print(f" ✗ Ошибок: {fail}") |
|
print(f" Всего: {len(src_hosts)}") |
|
|
|
if failed: |
|
print(f"\nХосты с ошибками:") |
|
for name, err in failed: |
|
print(f" - {name}: {err}") |