refactor 1st stage

This commit is contained in:
Volobuev Andrey
2025-06-27 17:05:33 +03:00
parent c0a6ca7769
commit 2b35038f73
7 changed files with 1306 additions and 632 deletions

View File

@@ -1,63 +1,90 @@
# [MODULE] Superset Dashboard Migration Script
# @contract: Автоматизирует процесс миграции и обновления дашбордов Superset между окружениями.
# @semantic_layers:
# 1. Конфигурация клиентов Superset для исходного и целевого окружений.
# 2. Определение правил трансформации конфигураций баз данных.
# 3. Экспорт дашборда, модификация YAML-файлов, создание нового архива и импорт.
# @coherence:
# - Использует `SupersetClient` для взаимодействия с API Superset.
# - Использует `SupersetLogger` для централизованного логирования.
# - Работает с `Pathlib` для управления файлами и директориями.
# - Интегрируется с `keyring` для безопасного хранения паролей.
# - Зависит от утилит `fileio` для обработки архивов и YAML-файлов.
# [IMPORTS] Локальные модули
from superset_tool.models import SupersetConfig
from superset_tool.client import SupersetClient
from superset_tool.utils.logger import SupersetLogger
from superset_tool.exceptions import AuthenticationError
from superset_tool.utils.fileio import save_and_unpack_dashboard, update_yamls, create_dashboard_export, create_temp_file,read_dashboard_from_disk
from superset_tool.exceptions import AuthenticationError, SupersetAPIError, NetworkError, DashboardNotFoundError
from superset_tool.utils.fileio import save_and_unpack_dashboard, update_yamls, create_dashboard_export, create_temp_file, read_dashboard_from_disk
# [IMPORTS] Стандартная библиотека
import os
import keyring
from pathlib import Path
import logging
log_dir = Path("H:\\dev\\Logs")
# [CONFIG] Инициализация глобального логгера
# @invariant: Логгер доступен для всех компонентов скрипта.
log_dir = Path("H:\\dev\\Logs") # [COHERENCE_NOTE] Убедитесь, что путь доступен.
logger = SupersetLogger(
log_dir=log_dir,
level=logging.INFO,
console=True
)
)
logger.info("[COHERENCE_CHECK_PASSED] Логгер инициализирован для скрипта миграции.")
database_config_click={"old":
{
"database_name": "Prod Clickhouse",
"sqlalchemy_uri": "clickhousedb+connect://clicketl:XXXXXXXXXX@rgm-s-khclk.hq.root.ad:443/dm",
"uuid": "b9b67cb5-9874-4dc6-87bd-354fc33be6f9",
"database_uuid": "b9b67cb5-9874-4dc6-87bd-354fc33be6f9",
"allow_ctas": "false",
"allow_cvas": "false",
"allow_dml": "false"
},
"new": {
"database_name": "Dev Clickhouse",
"sqlalchemy_uri": "clickhousedb+connect://dwhuser:XXXXXXXXXX@10.66.229.179:8123/dm",
"uuid": "e9fd8feb-cb77-4e82-bc1d-44768b8d2fc2",
"database_uuid": "e9fd8feb-cb77-4e82-bc1d-44768b8d2fc2",
"allow_ctas": "true",
"allow_cvas": "true",
"allow_dml": "true"
}
}
# [CONFIG] Конфигурация трансформации базы данных Clickhouse
# @semantic: Определяет, как UUID и URI базы данных Clickhouse должны быть изменены.
# @invariant: 'old' и 'new' должны содержать полные конфигурации.
database_config_click = {
"old": {
"database_name": "Prod Clickhouse",
"sqlalchemy_uri": "clickhousedb+connect://clicketl:XXXXXXXXXX@rgm-s-khclk.hq.root.ad:443/dm",
"uuid": "b9b67cb5-9874-4dc6-87bd-354fc33be6f9",
"database_uuid": "b9b67cb5-9874-4dc6-87bd-354fc33be6f9",
"allow_ctas": "false",
"allow_cvas": "false",
"allow_dml": "false"
},
"new": {
"database_name": "Dev Clickhouse",
"sqlalchemy_uri": "clickhousedb+connect://dwhuser:XXXXXXXXXX@10.66.229.179:8123/dm",
"uuid": "e9fd8feb-cb77-4e82-bc1d-44768b8d2fc2",
"database_uuid": "e9fd8feb-cb77-4e82-bc1d-44768b8d2fc2",
"allow_ctas": "true",
"allow_cvas": "true",
"allow_dml": "true"
}
}
logger.debug("[CONFIG] Конфигурация Clickhouse загружена.")
database_config_gp={"old":
{
"database_name": "Prod Greenplum",
"sqlalchemy_uri": "postgresql+psycopg2://viz_powerbi_gp_prod:XXXXXXXXXX@10.66.229.201:5432/dwh",
"uuid": "805132a3-e942-40ce-99c7-bee8f82f8aa8",
"database_uuid": "805132a3-e942-40ce-99c7-bee8f82f8aa8",
"allow_ctas": "true",
"allow_cvas": "true",
"allow_dml": "true"
},
"new": {
"database_name": "DEV Greenplum",
"sqlalchemy_uri": "postgresql+psycopg2://viz_superset_gp_dev:XXXXXXXXXX@10.66.229.171:5432/dwh",
"uuid": "97b97481-43c3-4181-94c5-b69eaaa1e11f",
"database_uuid": "97b97481-43c3-4181-94c5-b69eaaa1e11f",
"allow_ctas": "false",
"allow_cvas": "false",
"allow_dml": "false"
}
}
# [CONFIG] Конфигурация трансформации базы данных Greenplum
# @semantic: Определяет, как UUID и URI базы данных Greenplum должны быть изменены.
# @invariant: 'old' и 'new' должны содержать полные конфигурации.
database_config_gp = {
"old": {
"database_name": "Prod Greenplum",
"sqlalchemy_uri": "postgresql+psycopg2://viz_powerbi_gp_prod:XXXXXXXXXX@10.66.229.201:5432/dwh",
"uuid": "805132a3-e942-40ce-99c7-bee8f82f8aa8",
"database_uuid": "805132a3-e942-40ce-99c7-bee8f82f8aa8",
"allow_ctas": "true",
"allow_cvas": "true",
"allow_dml": "true"
},
"new": {
"database_name": "DEV Greenplum",
"sqlalchemy_uri": "postgresql+psycopg2://viz_superset_gp_dev:XXXXXXXXXX@10.66.229.171:5432/dwh",
"uuid": "97b97481-43c3-4181-94c5-b69eaaa1e11f",
"database_uuid": "97b97481-43c3-4181-94c5-b69eaaa1e11f",
"allow_ctas": "false",
"allow_cvas": "false",
"allow_dml": "false"
}
}
logger.debug("[CONFIG] Конфигурация Greenplum загружена.")
# Конфигурация для Dev
# [CONFIG] Конфигурация Superset API для Dev окружения
dev_config = SupersetConfig(
base_url="https://devta.bi.dwh.rusal.com/api/v1",
auth={
@@ -69,8 +96,9 @@ dev_config = SupersetConfig(
logger=logger,
verify_ssl=False
)
logger.debug(f"[CONFIG] Dev SupersetConfig создан для {dev_config.base_url}")
# Конфигурация для Prod
# [CONFIG] Конфигурация Superset API для Prod окружения
prod_config = SupersetConfig(
base_url="https://prodta.bi.dwh.rusal.com/api/v1",
auth={
@@ -82,8 +110,9 @@ prod_config = SupersetConfig(
logger=logger,
verify_ssl=False
)
logger.debug(f"[CONFIG] Prod SupersetConfig создан для {prod_config.base_url}")
# Конфигурация для Sandbox
# [CONFIG] Конфигурация Superset API для Sandbox окружения
sandbox_config = SupersetConfig(
base_url="https://sandboxta.bi.dwh.rusal.com/api/v1",
auth={
@@ -95,48 +124,87 @@ sandbox_config = SupersetConfig(
logger=logger,
verify_ssl=False
)
logger.debug(f"[CONFIG] Sandbox SupersetConfig создан для {sandbox_config.base_url}")
# Инициализация клиента
# [INIT] Инициализация клиентов Superset API
# @invariant: Все клиенты должны быть успешно инициализированы для дальнейшей работы.
try:
dev_client = SupersetClient(dev_config)
sandbox_client = SupersetClient(sandbox_config)
prod_client = SupersetClient(prod_config) # Не используется в текущем flow, но инициализирован.
logger.info("[COHERENCE_CHECK_PASSED] Клиенты Superset успешно инициализированы.")
except Exception as e:
logger.critical(f"[CRITICAL] Ошибка инициализации клиентов Superset: {str(e)}", exc_info=True)
exit(1) # Выход из скрипта при критической ошибке инициализации
dev_client = SupersetClient(dev_config)
sandbox_client = SupersetClient(sandbox_config)
prod_client = SupersetClient(prod_config)
# [CONFIG] Определение исходного и целевого клиентов для миграции
# [COHERENCE_NOTE] Эти переменные задают конкретную миграцию. Для параметризации можно использовать аргументы командной строки.
from_c = sandbox_client # Источник миграции
to_c = dev_client # Цель миграции
dashboard_slug = "FI0070" # Идентификатор дашборда для миграции
# dashboard_id = 53 # ID не нужен, если есть slug
from_c = sandbox_client
to_c = dev_client
dashboard_slug = "FI0070"
dashboard_id = 53
logger.info(f"[INFO] Конфигурация миграции: From '{from_c.config.base_url}' To '{to_c.config.base_url}' for dashboard slug '{dashboard_slug}'")
dashboard_meta = from_c.get_dashboard(dashboard_slug)
#print(dashboard_meta)
#print(dashboard_meta["dashboard_title"])
try:
# [ACTION] Получение метаданных исходного дашборда
logger.info(f"[INFO] Получение метаданных дашборда '{dashboard_slug}' из исходного окружения.")
dashboard_meta = from_c.get_dashboard(dashboard_slug)
dashboard_id = dashboard_meta["id"] # Получаем ID из метаданных
logger.info(f"[INFO] Найден дашборд '{dashboard_meta['dashboard_title']}' с ID: {dashboard_id}.")
dashboard_id = dashboard_meta["id"]
# [CONTEXT_MANAGER] Работа с временной директорией для обработки архива дашборда
with create_temp_file(suffix='.dir', logger=logger) as temp_root:
logger.info(f"[INFO] Создана временная директория: {temp_root}")
# [ANCHOR] EXPORT_DASHBOARD
# Экспорт дашборда во временную директорию ИЛИ чтение с диска
# [COHERENCE_NOTE] В текущем коде закомментирован экспорт и используется локальный файл.
# Для полноценной миграции следует использовать export_dashboard().
# zip_content, filename = from_c.export_dashboard(dashboard_id) # Предпочтительный путь для реальной миграции
# [DEBUG] Использование файла с диска для тестирования миграции
zip_db_path = r"C:\Users\VolobuevAA\Downloads\dashboard_export_20250616T174203.zip"
logger.warning(f"[WARN] Используется ЛОКАЛЬНЫЙ файл дашборда для миграции: {zip_db_path}. Это может привести к некогерентности, если файл устарел.")
zip_content, filename = read_dashboard_from_disk(zip_db_path, logger=logger)
# [ANCHOR] SAVE_AND_UNPACK
# Сохранение и распаковка во временную директорию
zip_path, unpacked_path = save_and_unpack_dashboard(
zip_content=zip_content,
original_filename=filename,
unpack=True,
logger=logger,
output_dir=temp_root
)
logger.info(f"[INFO] Дашборд распакован во временную директорию: {unpacked_path}")
# [ANCHOR] UPDATE_YAML_CONFIGS
# Обновление конфигураций баз данных в YAML-файлах
source_path = unpacked_path / Path(filename).stem # Путь к распакованному содержимому дашборда
db_configs_to_apply = [database_config_click, database_config_gp]
logger.info(f"[INFO] Применение трансформаций баз данных к YAML файлам в {source_path}...")
update_yamls(db_configs_to_apply, path=source_path, logger=logger)
logger.info("[INFO] YAML-файлы успешно обновлены.")
with create_temp_file(suffix='.dir', logger=logger) as temp_root:
# Экспорт дашборда во временную директорию
#zip_content, filename = from_c.export_dashboard(dashboard_id, logger=logger)
zip_db_path = r"C:\Users\VolobuevAA\Downloads\dashboard_export_20250616T174203.zip"
# [ANCHOR] CREATE_NEW_EXPORT_ARCHIVE
# Создание нового экспорта дашборда из модифицированных файлов
temp_zip = temp_root / f"{dashboard_slug}_migrated.zip" # Имя файла для импорта
logger.info(f"[INFO] Создание нового ZIP-архива для импорта: {temp_zip}")
create_dashboard_export(temp_zip, [source_path], logger=logger)
logger.info("[INFO] Новый ZIP-архив дашборда готов к импорту.")
zip_content, filename = read_dashboard_from_disk(zip_db_path, logger=logger)
# [ANCHOR] IMPORT_DASHBOARD
# Импорт обновленного дашборда в целевое окружение
logger.info(f"[INFO] Запуск импорта дашборда в целевое окружение {to_c.config.base_url}...")
import_result = to_c.import_dashboard(temp_zip)
logger.info(f"[COHERENCE_CHECK_PASSED] Дашборд '{dashboard_slug}' успешно импортирован/обновлен.", extra={"import_result": import_result})
# Сохранение и распаковка во временную директорию
zip_path, unpacked_path = save_and_unpack_dashboard(
zip_content=zip_content,
original_filename=filename,
unpack=True,
logger=logger,
output_dir=temp_root
)
# Обновление конфигураций
source_path = unpacked_path / Path(filename).stem
update_yamls([database_config_click,database_config_gp], path=source_path, logger=logger)
# Создание нового экспорта во временной директории
temp_zip = temp_root / f"{dashboard_slug}.zip"
create_dashboard_export(temp_zip, [source_path], logger=logger)
# Импорт обновленного дашборда
to_c.import_dashboard(temp_zip)
except (AuthenticationError, SupersetAPIError, NetworkError, DashboardNotFoundError) as e:
logger.error(f"[ERROR] Ошибка миграции дашборда: {str(e)}", exc_info=True, extra=e.context)
exit(1)
except Exception as e:
logger.critical(f"[CRITICAL] Фатальная и необработанная ошибка в скрипте миграции: {str(e)}", exc_info=True)
exit(1)
logger.info("[INFO] Процесс миграции завершен.")