Files
DevOpsLab/app/alembic/versions/009_migrate_roles_to_db.py
Сергей Антропов 1fbf9185a2 feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
2026-02-15 22:59:02 +03:00

201 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Migrate roles from filesystem to database
Revision ID: 009
Revises: 008
Create Date: 2024-01-06 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
import json
import yaml
from pathlib import Path
from datetime import datetime
import os
# revision identifiers, used by Alembic.
revision = '009'
down_revision = '008'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Перенос ролей из файловой системы в БД и в alembic/roles/"""
connection = op.get_bind()
# Определяем пути
alembic_dir = Path(__file__).parent.parent
roles_dir_alembic = alembic_dir / "roles"
roles_dir_alembic.mkdir(exist_ok=True)
# Определяем исходную папку с ролями
project_root = Path(os.getenv("PROJECT_ROOT", "/workspace"))
roles_dir_source = project_root / "roles"
# Если исходная папка не найдена, пробуем относительный путь
if not roles_dir_source.exists():
# Пробуем найти относительно alembic
possible_paths = [
alembic_dir.parent.parent / "roles",
Path.cwd() / "roles",
Path("/workspace") / "roles"
]
for path in possible_paths:
if path.exists():
roles_dir_source = path
break
if not roles_dir_source.exists():
print(f"⚠️ Папка roles не найдена: {roles_dir_source}")
return
print(f"📁 Исходная папка ролей: {roles_dir_source}")
print(f"📁 Целевая папка ролей: {roles_dir_alembic}")
# Функция для чтения файла с обработкой ошибок
def read_file_safe(file_path: Path) -> str:
try:
return file_path.read_text(encoding='utf-8')
except Exception as e:
print(f"⚠️ Ошибка чтения файла {file_path}: {e}")
return ""
# Функция для сбора всех файлов роли
def collect_role_files(role_dir: Path) -> dict:
"""Собирает все файлы роли в словарь {relative_path: content}"""
role_content = {}
# Стандартные файлы и папки
standard_files = {
"tasks/main.yml": "tasks/main.yml",
"handlers/main.yml": "handlers/main.yml",
"defaults/main.yml": "defaults/main.yml",
"vars/main.yml": "vars/main.yml",
"meta/main.yml": "meta/main.yml",
"README.md": "README.md"
}
# Читаем стандартные файлы
for file_path, key in standard_files.items():
full_path = role_dir / file_path
if full_path.exists():
role_content[key] = read_file_safe(full_path)
# Читаем все файлы из templates/
templates_dir = role_dir / "templates"
if templates_dir.exists():
for template_file in templates_dir.rglob("*"):
if template_file.is_file():
rel_path = template_file.relative_to(role_dir)
role_content[str(rel_path)] = read_file_safe(template_file)
# Читаем все файлы из files/
files_dir = role_dir / "files"
if files_dir.exists():
for file_item in files_dir.rglob("*"):
if file_item.is_file():
rel_path = file_item.relative_to(role_dir)
role_content[str(rel_path)] = read_file_safe(file_item)
# Читаем все файлы из library/ (если есть)
library_dir = role_dir / "library"
if library_dir.exists():
for lib_file in library_dir.rglob("*"):
if lib_file.is_file():
rel_path = lib_file.relative_to(role_dir)
role_content[str(rel_path)] = read_file_safe(lib_file)
return role_content
# Функция для извлечения метаданных из meta/main.yml
def extract_metadata(role_content: dict) -> tuple:
"""Извлекает метаданные из meta/main.yml"""
meta_content = role_content.get("meta/main.yml", "")
if not meta_content:
return None, None, None
try:
meta_data = yaml.safe_load(meta_content)
if not meta_data or not isinstance(meta_data, dict):
return None, None, None
galaxy_info = meta_data.get("galaxy_info", {})
author = galaxy_info.get("author", "")
description = galaxy_info.get("description", "")
platforms = galaxy_info.get("platforms", [])
return author, description, platforms
except Exception as e:
print(f"⚠️ Ошибка парсинга meta/main.yml: {e}")
return None, None, None
# Обрабатываем каждую роль
migrated_count = 0
for role_dir in roles_dir_source.iterdir():
if not role_dir.is_dir() or role_dir.name.startswith('.'):
continue
role_name = role_dir.name
print(f"📦 Обработка роли: {role_name}")
# Собираем все файлы роли
role_content = collect_role_files(role_dir)
if not role_content:
print(f"⚠️ Роль {role_name} не содержит файлов, пропускаем")
continue
# Извлекаем метаданные
author, description, platforms = extract_metadata(role_content)
# Копируем роль в alembic/roles/
target_role_dir = roles_dir_alembic / role_name
target_role_dir.mkdir(exist_ok=True)
# Копируем структуру папок и файлов
for rel_path, content in role_content.items():
target_file = target_role_dir / rel_path
target_file.parent.mkdir(parents=True, exist_ok=True)
try:
target_file.write_text(content, encoding='utf-8')
except Exception as e:
print(f"⚠️ Ошибка записи файла {target_file}: {e}")
# Сохраняем в БД
try:
connection.execute(
sa.text("""
INSERT INTO roles (name, description, content, is_global, is_personal, author, platforms, galaxy_info, status, created_at, updated_at)
VALUES (:name, :description, :content, :is_global, :is_personal, :author, :platforms, :galaxy_info, :status, :created_at, :updated_at)
ON CONFLICT (name) DO NOTHING
"""),
{
'name': role_name,
'description': description or f"Роль {role_name}",
'content': json.dumps(role_content),
'is_global': True, # По умолчанию все роли глобальные
'is_personal': False,
'author': author,
'platforms': json.dumps(platforms) if platforms else None,
'galaxy_info': json.dumps({"galaxy_info": {"author": author, "description": description, "platforms": platforms}}) if author or description or platforms else None,
'status': 'active',
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
migrated_count += 1
print(f"✅ Роль {role_name} успешно мигрирована")
except Exception as e:
print(f"❌ Ошибка при миграции роли {role_name}: {e}")
print(f"\n✅ Миграция завершена. Перенесено ролей: {migrated_count}")
def downgrade() -> None:
"""Откат миграции - удаление ролей из БД"""
connection = op.get_bind()
connection.execute(sa.text("DELETE FROM roles"))
print("⚠️ Роли удалены из БД. Файлы в alembic/roles/ остаются для безопасности.")