feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
200
app/alembic/versions/009_migrate_roles_to_db.py
Normal file
200
app/alembic/versions/009_migrate_roles_to_db.py
Normal file
@@ -0,0 +1,200 @@
|
||||
"""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/ остаются для безопасности.")
|
||||
Reference in New Issue
Block a user