feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

194
app/scripts/load_presets.py Normal file
View File

@@ -0,0 +1,194 @@
"""
Скрипт для загрузки пресетов из файловой системы в базу данных
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import sys
import os
from pathlib import Path
# Добавляем путь к приложению
app_path = Path(__file__).parent.parent
sys.path.insert(0, str(app_path))
# Также добавляем родительскую директорию для импорта app
if str(app_path.parent) not in sys.path:
sys.path.insert(0, str(app_path.parent))
from sqlalchemy import create_engine, text
from sqlalchemy.dialects.postgresql import JSON
import yaml
import json
from datetime import datetime
# Импортируем настройки
try:
from app.core.config import settings
except ImportError:
# Если не получается импортировать, используем значения по умолчанию
class Settings:
PROJECT_ROOT = Path("/workspace")
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://devopslab:devopslab123@postgres:5432/devopslab")
settings = Settings()
def load_presets():
"""Загрузка пресетов из файловой системы в БД"""
# Создаем подключение к БД
db_url = str(settings.DATABASE_URL).replace("postgresql+asyncpg://", "postgresql://")
engine = create_engine(db_url)
connection = engine.connect()
try:
# Получаем путь к папке presets
# Пресеты теперь находятся в alembic/presets
script_dir = Path(__file__).parent
alembic_dir = script_dir.parent / "alembic"
presets_dir = alembic_dir / "presets"
# Если не найдено в alembic, пробуем старый путь (для обратной совместимости)
if not presets_dir.exists():
project_root = Path(settings.PROJECT_ROOT)
old_presets_dir = project_root / "molecule" / "presets"
if old_presets_dir.exists():
presets_dir = old_presets_dir
print(f"⚠️ Используется старый путь: {presets_dir}")
k8s_presets_dir = presets_dir / "k8s"
print(f"📁 Поиск пресетов в: {presets_dir}")
loaded_count = 0
skipped_count = 0
error_count = 0
# Основные preset'ы
if presets_dir.exists():
for preset_file in presets_dir.glob("*.yml"):
if preset_file.name == "deploy.yml":
continue
try:
with open(preset_file) as f:
content = f.read()
preset_data = yaml.safe_load(content) or {}
# Извлечение описания из комментария
description = None
for line in content.split('\n'):
if line.strip().startswith('#description:'):
description = line.split('#description:')[1].strip()
break
# Проверка существования в БД
result = connection.execute(
text("SELECT id FROM presets WHERE name = :name"),
{"name": preset_file.stem}
)
if result.fetchone():
print(f"⏭️ Пропущен (уже существует): {preset_file.stem}")
skipped_count += 1
continue
# Вставка в БД
# Преобразуем dict/list в JSON строки для PostgreSQL
hosts_json = json.dumps(preset_data.get('hosts', []))
images_json = json.dumps(preset_data.get('images', {}))
systemd_defaults_json = json.dumps(preset_data.get('systemd_defaults', {}))
kind_clusters_json = json.dumps(preset_data.get('kind_clusters', []))
connection.execute(
text("""
INSERT INTO presets (name, category, description, content, docker_network, hosts, images, systemd_defaults, kind_clusters, created_at, updated_at)
VALUES (:name, :category, :description, :content, :docker_network, CAST(:hosts AS jsonb), CAST(:images AS jsonb), CAST(:systemd_defaults AS jsonb), CAST(:kind_clusters AS jsonb), :created_at, :updated_at)
"""),
{
'name': preset_file.stem,
'category': 'main',
'description': description,
'content': content,
'docker_network': preset_data.get('docker_network'),
'hosts': hosts_json,
'images': images_json,
'systemd_defaults': systemd_defaults_json,
'kind_clusters': kind_clusters_json,
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
connection.commit()
print(f"✅ Загружен: {preset_file.stem}")
loaded_count += 1
except Exception as e:
print(f"❌ Ошибка при загрузке {preset_file.name}: {e}")
error_count += 1
# K8s preset'ы
if k8s_presets_dir.exists():
for preset_file in k8s_presets_dir.glob("*.yml"):
try:
with open(preset_file) as f:
content = f.read()
preset_data = yaml.safe_load(content) or {}
# Извлечение описания из комментария
description = None
for line in content.split('\n'):
if line.strip().startswith('#description:'):
description = line.split('#description:')[1].strip()
break
# Проверка существования в БД
result = connection.execute(
text("SELECT id FROM presets WHERE name = :name"),
{"name": preset_file.stem}
)
if result.fetchone():
print(f"⏭️ Пропущен (уже существует): {preset_file.stem}")
skipped_count += 1
continue
# Вставка в БД
# Преобразуем dict/list в JSON строки для PostgreSQL
hosts_json = json.dumps(preset_data.get('hosts', []))
images_json = json.dumps(preset_data.get('images', {}))
systemd_defaults_json = json.dumps(preset_data.get('systemd_defaults', {}))
kind_clusters_json = json.dumps(preset_data.get('kind_clusters', []))
connection.execute(
text("""
INSERT INTO presets (name, category, description, content, docker_network, hosts, images, systemd_defaults, kind_clusters, created_at, updated_at)
VALUES (:name, :category, :description, :content, :docker_network, CAST(:hosts AS jsonb), CAST(:images AS jsonb), CAST(:systemd_defaults AS jsonb), CAST(:kind_clusters AS jsonb), :created_at, :updated_at)
"""),
{
'name': preset_file.stem,
'category': 'k8s',
'description': description,
'content': content,
'docker_network': preset_data.get('docker_network'),
'hosts': hosts_json,
'images': images_json,
'systemd_defaults': systemd_defaults_json,
'kind_clusters': kind_clusters_json,
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
connection.commit()
print(f"✅ Загружен: {preset_file.stem} (k8s)")
loaded_count += 1
except Exception as e:
print(f"❌ Ошибка при загрузке k8s preset {preset_file.name}: {e}")
error_count += 1
print(f"\n📊 Итого:")
print(f" ✅ Загружено: {loaded_count}")
print(f" ⏭️ Пропущено: {skipped_count}")
print(f" ❌ Ошибок: {error_count}")
finally:
connection.close()
engine.dispose()
if __name__ == "__main__":
load_presets()