2 Commits

Author SHA1 Message Date
Sergey Antropoff
700452f681 make: добавлен setup и усилен podman role test
Добавлен таргет make setup для создания vault/.vault с интерактивным вводом пароля.
В make role test добавлены проверки локального ansible-controller, запрет pull и корректный exit code.

Made-with: Cursor
2026-03-25 12:12:49 +03:00
Sergey Antropoff
05881e8d74 podman: переход на Podman, Minikube, локальные образы и док для новичков
- Molecule: драйвер delegated, коллекция containers.podman, create/destroy/verify на Podman
- Makefile: все вызовы docker заменены на podman, сокет /run/podman/podman.sock
- Сборка образов: podman build (без buildx), buildall/buildall-image — только локально без push
- Ansible-controller: Podman в образе, docker-compose на podman compose, сокет Podman
- K8s: Kind заменён на Minikube (драйвер podman), скрипты и Makefile обновлены
- Пресеты: проверка локальных образов, без podman pull (registry запрещён)
- Документация: docs/podman.md, docs/quickstart-for-dummies.md (роли, плейбук, линт, тесты, пресеты, инвентори)
- README: ссылка на quickstart-for-dummies

Made-with: Cursor
2026-03-11 19:59:47 +03:00
245 changed files with 945 additions and 40155 deletions

933
Makefile

File diff suppressed because it is too large Load Diff

104
README.md
View File

@@ -8,15 +8,12 @@
DevOpsLab - это универсальная DevOps платформа для разработки, тестирования и развертывания инфраструктуры. Система объединяет Ansible роли, Docker контейнеры и Kubernetes кластеры в единую среду для автоматизации и управления инфраструктурой. DevOpsLab - это универсальная DevOps платформа для разработки, тестирования и развертывания инфраструктуры. Система объединяет Ansible роли, Docker контейнеры и Kubernetes кластеры в единую среду для автоматизации и управления инфраструктурой.
**Новое в версии 3.0:** Полнофункциональный веб-интерфейс для управления ролями, тестирования и деплоя через браузер. Всё работает в Docker - ничего не нужно устанавливать локально!
**Ключевые компоненты:** **Ключевые компоненты:**
- **Ansible** - автоматизация конфигурации и развертывания - **Ansible** - автоматизация конфигурации и развертывания
- **Docker** - контейнеризация для изоляции и переносимости - **Docker** - контейнеризация для изоляции и переносимости
- **Molecule** - тестирование Ansible ролей - **Molecule** - тестирование Ansible ролей
- **Kubernetes (Kind)** - локальные K8s кластеры для разработки - **Kubernetes (Kind)** - локальные K8s кластеры для разработки
- **Multi-arch поддержка** - сборка для amd64 и arm64 архитектур - **Multi-arch поддержка** - сборка для amd64 и arm64 архитектур
- **Web UI** - современный веб-интерфейс для управления ролями, тестирования и деплоя
## ✨ Ключевые возможности ## ✨ Ключевые возможности
@@ -42,20 +39,6 @@ DevOpsLab - это универсальная DevOps платформа для
- **Port-forward** для доступа к сервисам - **Port-forward** для доступа к сервисам
- **Детальный мониторинг** состояния кластера - **Детальный мониторинг** состояния кластера
### 🌐 Web UI
- **Современный веб-интерфейс** на FastAPI с HTMX и Bootstrap 5
- **Управление ролями** - создание, редактирование, просмотр с подсветкой синтаксиса
- **Тестирование ролей** - запуск тестов с live логами через WebSocket
- **Управление preset'ами** - создание и настройка окружений тестирования
- **Деплой на серверы** - развертывание с отображением логов в реальном времени
- **Импорт/экспорт ролей** - работа с Git репозиториями и Ansible Galaxy
- **Управление Vault** - шифрование и расшифровка секретов
- **Playbook редактор** - создание и редактирование playbook с валидацией
- **Dockerfile редактор** - управление Docker образами для тестирования
- **История выполнения** - сохранение результатов тестов и деплоев
- **Аутентификация** - безопасный вход с JWT токенами
- **Работает в Docker** - ничего не нужно устанавливать локально
## 📁 Структура проекта ## 📁 Структура проекта
``` ```
@@ -189,14 +172,6 @@ DevOpsLab/
│ ├── setup-cicd.sh # Настройка CI/CD │ ├── setup-cicd.sh # Настройка CI/CD
│ ├── test-custom-images.sh # Тестирование образов │ ├── test-custom-images.sh # Тестирование образов
│ └── update-playbooks.sh # Обновление playbook'ов │ └── update-playbooks.sh # Обновление playbook'ов
├── app/ # Веб-интерфейс (FastAPI)
│ ├── api/ # API endpoints
│ ├── templates/ # HTMX шаблоны
│ ├── static/ # Статические файлы (CSS, JS)
│ ├── services/ # Бизнес-логика
│ ├── models/ # Модели базы данных
│ ├── docker-compose.yml # Docker Compose конфигурация
│ └── ...
├── docs/ # Документация ├── docs/ # Документация
│ ├── kubernetes-kind.md # Руководство по Kubernetes │ ├── kubernetes-kind.md # Руководство по Kubernetes
│ ├── k8s-scripts.md # Описание K8s скриптов │ ├── k8s-scripts.md # Описание K8s скриптов
@@ -209,7 +184,6 @@ DevOpsLab/
│ ├── linting-guide.md # Руководство по линтингу │ ├── linting-guide.md # Руководство по линтингу
│ ├── platform-support.md # Поддержка платформ │ ├── platform-support.md # Поддержка платформ
│ ├── monitoring.md # Мониторинг │ ├── monitoring.md # Мониторинг
│ ├── web-interface-*.md # Документация веб-интерфейса
│ └── ... │ └── ...
├── manifests/ # Kubernetes манифесты ├── manifests/ # Kubernetes манифесты
│ └── test-grafana-ingress.yaml │ └── test-grafana-ingress.yaml
@@ -230,26 +204,7 @@ git clone <repository-url>
cd DevOpsLab cd DevOpsLab
``` ```
### 2. Запуск веб-интерфейса (рекомендуется) ### 2. Тестирование ролей
```bash
# Перейти в директорию веб-интерфейса
cd app
# Запустить через docker-compose
make up
# или
docker-compose up -d
# Открыть в браузере
# http://localhost:8000
```
**Логин по умолчанию:** `admin` / `admin` (рекомендуется сменить при первом входе)
**Подробная документация:** [docs/web-interface-quickstart.md](docs/web-interface-quickstart.md)
### 3. Тестирование ролей
```bash ```bash
# Тестирование с default preset (2 хоста) # Тестирование с default preset (2 хоста)
@@ -262,7 +217,7 @@ make role test minimal
make role test my-custom-preset make role test my-custom-preset
``` ```
### 4. Проверка синтаксиса ### 3. Проверка синтаксиса
```bash ```bash
# Проверка всех ролей # Проверка всех ролей
@@ -273,7 +228,7 @@ make role lint docker
make role lint ping make role lint ping
``` ```
### 5. Работа с Kubernetes ### 4. Работа с Kubernetes
```bash ```bash
# Создание Kind кластера с аддонами # Создание Kind кластера с аддонами
@@ -294,45 +249,6 @@ make k8s destroy kubernetes
**Подробная документация:** [docs/kubernetes-kind.md](docs/kubernetes-kind.md) **Подробная документация:** [docs/kubernetes-kind.md](docs/kubernetes-kind.md)
## 🌐 Веб-интерфейс
DevOpsLab включает полнофункциональный веб-интерфейс для управления всеми аспектами работы с ролями, тестированием и деплоем.
### Основные возможности
- **Управление ролями** - создание, редактирование, просмотр с подсветкой синтаксиса (CodeMirror)
- **Тестирование ролей** - запуск тестов с live логами через WebSocket
- **Управление preset'ами** - создание и настройка окружений тестирования
- **Деплой на серверы** - развертывание с отображением логов в реальном времени
- **Импорт/экспорт ролей** - работа с Git репозиториями и Ansible Galaxy
- **Управление Vault** - шифрование и расшифровка секретов через веб-интерфейс
- **Playbook редактор** - создание и редактирование playbook с валидацией YAML
- **Dockerfile редактор** - управление Docker образами для тестирования
- **История выполнения** - сохранение результатов тестов и деплоев в PostgreSQL
- **Аутентификация** - безопасный вход с JWT токенами
### Быстрый запуск
```bash
# Перейти в директорию веб-интерфейса
cd app
# Запустить через docker-compose
make up
# или
docker-compose up -d
# Открыть в браузере
# http://localhost:8000
```
**Логин по умолчанию:** `admin` / `admin` (рекомендуется сменить при первом входе)
**Подробная документация:**
- [docs/web-interface-quickstart.md](docs/web-interface-quickstart.md) - Быстрый старт
- [docs/web-interface-docker.md](docs/web-interface-docker.md) - Запуск в Docker
- [docs/WEB_INTERFACE_DETAILS.md](docs/WEB_INTERFACE_DETAILS.md) - Детали работы
## 📚 Доступные роли ## 📚 Доступные роли
### Docker ### Docker
@@ -715,11 +631,10 @@ make custom-images # справка по собственным
## 📖 Документация ## 📖 Документация
**Полный индекс документации:** [docs/README.md](docs/README.md)
### Основная документация ### Основная документация
- **[docs/getting-started.md](docs/getting-started.md)** - Быстрый старт - **[docs/getting-started.md](docs/getting-started.md)** - Быстрый старт
- **[docs/quickstart-for-dummies.md](docs/quickstart-for-dummies.md)** - Пошаговое руководство для новичков (роли, плейбук, линт, тесты, пресеты, инвентори, деплой)
- **[docs/molecule-guide.md](docs/molecule-guide.md)** - Руководство по Molecule - **[docs/molecule-guide.md](docs/molecule-guide.md)** - Руководство по Molecule
- **[docs/creating-roles.md](docs/creating-roles.md)** - Создание ролей - **[docs/creating-roles.md](docs/creating-roles.md)** - Создание ролей
- **[docs/devops-role.md](docs/devops-role.md)** - Универсальная роль devops для настройки пользователей и SSH - **[docs/devops-role.md](docs/devops-role.md)** - Универсальная роль devops для настройки пользователей и SSH
@@ -755,17 +670,6 @@ make custom-images # справка по собственным
- **[docs/examples.md](docs/examples.md)** - Примеры использования - **[docs/examples.md](docs/examples.md)** - Примеры использования
- **[CHANGELOG.md](CHANGELOG.md)** - История изменений - **[CHANGELOG.md](CHANGELOG.md)** - История изменений
### Веб-интерфейс
- **[docs/web-interface-quickstart.md](docs/web-interface-quickstart.md)** - Быстрый старт веб-интерфейса
- **[docs/web-interface-docker.md](docs/web-interface-docker.md)** - Запуск веб-интерфейса в Docker
- **[docs/web-interface-readme.md](docs/web-interface-readme.md)** - Общая документация веб-интерфейса
- **[docs/WEB_INTERFACE_PROPOSAL.md](docs/WEB_INTERFACE_PROPOSAL.md)** - Полное предложение по веб-интерфейсу
- **[docs/WEB_INTERFACE_DETAILS.md](docs/WEB_INTERFACE_DETAILS.md)** - Детали работы веб-интерфейса
- **[docs/WEB_INTERFACE_DEPLOY_IMPORT_EXPORT.md](docs/WEB_INTERFACE_DEPLOY_IMPORT_EXPORT.md)** - Деплой, импорт и экспорт через веб-интерфейс
- **[docs/web-interface-deployment-status.md](docs/web-interface-deployment-status.md)** - Статус развертывания
- **[docs/web-interface-status.md](docs/web-interface-status.md)** - Текущий статус веб-интерфейса
### Документация по ролям ### Документация по ролям
- **[roles/docker/README.md](roles/docker/README.md)** - Документация роли Docker - **[roles/docker/README.md](roles/docker/README.md)** - Документация роли Docker

View File

@@ -1,45 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
*.egg-info/
.installed.cfg
*.egg
# Environment
.env
.env.local
# Database
*.db
*.sqlite
*.sqlite3
# Logs
logs/
*.log
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore

48
app/.gitignore vendored
View File

@@ -1,48 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Environment
.env
.env.local
# Database
*.db
*.sqlite
*.sqlite3
# Logs
logs/
*.log
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db

View File

@@ -1,61 +0,0 @@
# Dockerfile для веб-интерфейса DevOpsLab
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
FROM python:3.11-slim
# Метаданные
LABEL maintainer="Сергей Антропов"
LABEL description="Веб-интерфейс для DevOpsLab"
LABEL site="https://devops.org.ru"
# Установка системных зависимостей
RUN apt-get update && apt-get install -y \
gcc \
g++ \
make \
git \
curl \
ca-certificates \
gnupg \
lsb-release \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# Установка Docker CLI для управления контейнерами через socket
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install -y docker-ce-cli docker-buildx-plugin \
&& rm -rf /var/lib/apt/lists/*
# Создание рабочей директории
WORKDIR /app
# Копирование requirements и установка зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копирование приложения
# Создаем структуру: /app/app/ для правильных импортов
RUN mkdir -p /app/app
COPY . /app/app/
# main.py остается в /app/app/ для правильных импортов
# Создание директории для логов
RUN mkdir -p logs
# Переменные окружения
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONPATH=/app
ENV TZ=Europe/Moscow
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Порт приложения
EXPOSE 8000
# Команда запуска
# main.py находится в /app/app/, поэтому используем app.main
# --reload-dir указывает на всю папку app для отслеживания изменений
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--reload-dir", "/app/app"]

View File

@@ -1,34 +0,0 @@
# Dockerfile для сборщика Docker образов
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
FROM docker:24-dind
# Метаданные
LABEL maintainer="Сергей Антропов"
LABEL description="Контейнер для сборки Docker образов"
LABEL site="https://devops.org.ru"
# Установка Python и зависимостей
RUN apk add --no-cache \
python3 \
py3-pip \
git \
curl \
bash
# Установка Docker Buildx
RUN mkdir -p ~/.docker/cli-plugins/ && \
curl -SL https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \
chmod +x ~/.docker/cli-plugins/docker-buildx
# Создание рабочей директории
WORKDIR /workspace
# Переменные окружения
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV DOCKER_BUILDKIT=1
# Команда по умолчанию (будет переопределена)
CMD ["tail", "-f", "/dev/null"]

View File

@@ -1,68 +0,0 @@
# Makefile для управления веб-интерфейсом
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
.PHONY: help build up down restart logs shell clean
# Переменные
COMPOSE_FILE = docker-compose.yml
COMPOSE_OVERRIDE = docker-compose.override.yml
COMPOSE = docker-compose -f $(COMPOSE_FILE)
ifneq ($(wildcard $(COMPOSE_OVERRIDE)),)
COMPOSE += -f $(COMPOSE_OVERRIDE)
endif
help:
@echo "Доступные команды:"
@echo " make build - Собрать образы"
@echo " make up - Запустить контейнеры"
@echo " make down - Остановить контейнеры"
@echo " make restart - Перезапустить контейнеры"
@echo " make logs - Показать логи"
@echo " make shell - Открыть shell в контейнере web"
@echo " make clean - Очистить контейнеры и volumes"
@echo " make rebuild - Пересобрать и перезапустить"
@echo " make migrate - Применить миграции БД"
@echo " make load-presets - Импортировать пресеты из файловой системы"
build:
$(COMPOSE) build
up:
$(COMPOSE) up -d
down:
$(COMPOSE) down
restart:
$(COMPOSE) restart
logs:
$(COMPOSE) logs -f web
logs-all:
$(COMPOSE) logs -f
shell:
$(COMPOSE) exec web bash
shell-celery:
$(COMPOSE) exec celery-worker bash
rebuild:
$(COMPOSE) down
$(COMPOSE) build --no-cache
$(COMPOSE) up -d
clean:
$(COMPOSE) down -v
docker system prune -f
status:
$(COMPOSE) ps
migrate:
$(COMPOSE) exec web bash -c "cd /app/app && alembic upgrade head"
load-presets:
$(COMPOSE) exec web bash -c "cd /app/app && python scripts/load_presets.py"

View File

@@ -1,114 +0,0 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = /app/app/alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -1,9 +0,0 @@
---
# Ansible Collections for Molecule Universal
collections:
- name: community.docker
version: ">=3.0.0"
- name: community.general
version: ">=7.0.0"
- name: ansible.posix
version: ">=1.5.4"

View File

@@ -1,108 +0,0 @@
"""
Alembic environment configuration
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import os
import sys
# Добавляем путь к приложению
app_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, app_dir)
# Также добавляем родительскую директорию для правильных импортов
parent_dir = os.path.dirname(app_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# Устанавливаем URL базы данных из переменных окружения или настроек
db_url = os.getenv("DATABASE_URL", "postgresql://devopslab:devopslab123@postgres:5432/devopslab")
if db_url.startswith("postgresql+asyncpg://"):
db_url = db_url.replace("postgresql+asyncpg://", "postgresql://")
config.set_main_option("sqlalchemy.url", db_url)
# Импортируем модели для autogenerate
try:
from app.models.user import Base as UserBase
from app.models.database import Base as DatabaseBase
# Объединяем метаданные
target_metadata = UserBase.metadata
# Добавляем таблицы из database.py
for table_name, table in DatabaseBase.metadata.tables.items():
if table_name not in target_metadata.tables:
target_metadata._add_table(table_name, table.schema, table)
except ImportError:
# Если не удалось импортировать, используем только UserBase
try:
from app.models.user import Base
target_metadata = Base.metadata
except ImportError:
# Если и это не работает, создаем пустые метаданные
from sqlalchemy import MetaData
target_metadata = MetaData()
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -1,106 +0,0 @@
---
# COD пресет с 6 контейнерами (Ubuntu + Debian + Alt + Astra + CentOS + RHEL)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Описание: Пресет для тестирования ролей на различных ОС
# Использует образы Ubuntu, Debian, Alt, Astra, CentOS, RHEL (все ARM64)
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# Используем стабильные образы Ubuntu, Debian, Alt, Astra, CentOS, RHEL (все ARM64)
images:
ubuntu: "inecs/ansible-lab:ubuntu22-latest"
debian: "inecs/ansible-lab:debian12-latest"
alt: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-arm64-latest" # ARM64 образ
centos: "inecs/ansible-lab:centos9-latest" # ARM64 образ
rhel: "inecs/ansible-lab:rhel-latest" # ARM64 образ
# Настройки для ARM64
# Используем нативные ARM64 образы
platform: "linux/arm64"
# Настройки Docker для ARM64
docker_options:
platform: "linux/arm64"
systemd_defaults:
privileged: true
command: "/bin/bash -c \"while true; do sleep 30; done\""
platform: "linux/arm64" # Используем ARM64
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Принудительная установка платформы для всех операций Docker
docker_platform: "linux/amd64"
hosts:
# =============================================================================
# UBUNTU СЕРВЕРЫ
# =============================================================================
#- name: ubuntu1
# family: ubuntu
# groups: [cod, ubuntu]
# platform: "linux/arm64" # Используем ARM64
# docker_options:
# platform: "linux/arm64"
# docker_platform: "linux/arm64"
# =============================================================================
# DEBIAN СЕРВЕРЫ
# =============================================================================
#- name: debian1
# family: debian
# groups: [cod, debian]
# platform: "linux/arm64" # Используем ARM64
# docker_options:
# platform: "linux/arm64"
# docker_platform: "linux/arm64"
# =============================================================================
# ALT СЕРВЕРЫ
# =============================================================================
- name: alt1
family: alt
groups: [cod, alt]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# ASTRA СЕРВЕРЫ (ARM64)
# =============================================================================
- name: astra1
family: astra
groups: [cod, astra]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# CENTOS СЕРВЕРЫ (ARM64)
# =============================================================================
#- name: centos1
# family: centos
# groups: [cod, centos]
# platform: "linux/arm64" # Используем ARM64
# docker_options:
# platform: "linux/arm64"
# docker_platform: "linux/arm64"
# =============================================================================
# RHEL СЕРВЕРЫ (ARM64)
# =============================================================================
#- name: rhel1
# family: rhel
# groups: [cod, rhel]
# platform: "linux/arm64" # Используем ARM64
# docker_options:
# platform: "linux/arm64"
# docker_platform: "linux/arm64"

View File

@@ -1,44 +0,0 @@
---
#description: Стандартный пресет по умолчанию для тестирования с 2 хостами (Ubuntu + Debian)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Стандартный набор - 2 хоста для базового тестирования (стабильные ОС)
- name: u1
family: ubuntu22
groups: [test, web]
- name: u2
family: debian12
groups: [test, web]

View File

@@ -1,185 +0,0 @@
---
#description: Пресет для тестирования всех доступных образов (9 хостов)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Примечание: Astra Linux и RedOS поддерживают только linux/amd64
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian-based системы
- name: ubuntu20-test
family: ubuntu2220
groups: [test, debian, ubuntu]
publish:
- "8080:80"
env:
TEST_OS: "Ubuntu 20.04"
TEST_FAMILY: "Debian"
- name: ubuntu22-test
family: ubuntu22
groups: [test, debian, ubuntu]
publish:
- "8081:80"
env:
TEST_OS: "Ubuntu 22.04"
TEST_FAMILY: "Debian"
- name: ubuntu24-test
family: ubuntu2224
groups: [test, debian, ubuntu]
publish:
- "8082:80"
env:
TEST_OS: "Ubuntu 24.04"
TEST_FAMILY: "Debian"
- name: debian9-test
family: debian129
groups: [test, debian]
publish:
- "8083:80"
env:
TEST_OS: "Debian 9"
TEST_FAMILY: "Debian"
- name: debian10-test
family: debian1210
groups: [test, debian]
publish:
- "8084:80"
env:
TEST_OS: "Debian 10"
TEST_FAMILY: "Debian"
- name: debian11-test
family: debian1211
groups: [test, debian]
publish:
- "8085:80"
env:
TEST_OS: "Debian 11"
TEST_FAMILY: "Debian"
- name: debian12-test
family: debian12
groups: [test, debian]
publish:
- "8086:80"
env:
TEST_OS: "Debian 12"
TEST_FAMILY: "Debian"
- name: alt-test
family: alt
groups: [test, altlinux]
publish:
- "8082:80"
env:
TEST_OS: "Alt Linux"
TEST_FAMILY: "Altlinux"
- name: astra-test
family: astra
groups: [test, astra]
supported_platforms: ["linux/amd64"] # Только amd64
publish:
- "8083:80"
env:
TEST_OS: "Astra Linux"
TEST_FAMILY: "Astra Linux"
# RHEL-based системы
- name: centos7-test
family: centos97
groups: [test, rhel, centos]
publish:
- "8090:80"
env:
TEST_OS: "CentOS 7"
TEST_FAMILY: "RedHat"
- name: centos8-test
family: centos98
groups: [test, rhel, centos]
publish:
- "8091:80"
env:
TEST_OS: "CentOS 8"
TEST_FAMILY: "RedHat"
- name: centos9-test
family: centos99
groups: [test, rhel, centos]
publish:
- "8092:80"
env:
TEST_OS: "CentOS 9"
TEST_FAMILY: "RedHat"
- name: rhel-test
family: rhel
groups: [test, rhel]
publish:
- "8085:80"
env:
TEST_OS: "RHEL"
TEST_FAMILY: "RedHat"
- name: alma-test
family: alma
groups: [test, rhel]
publish:
- "8086:80"
env:
TEST_OS: "AlmaLinux"
TEST_FAMILY: "RedHat"
- name: rocky-test
family: rocky
groups: [test, rhel]
publish:
- "8087:80"
env:
TEST_OS: "Rocky Linux"
TEST_FAMILY: "RedHat"
- name: redos-test
family: redos
groups: [test, rhel]
supported_platforms: ["linux/amd64"] # Только amd64
publish:
- "8088:80"
env:
TEST_OS: "RedOS"
TEST_FAMILY: "RedHat"

View File

@@ -1,52 +0,0 @@
---
#description: Пресет со всеми версиями CentOS (7, 8, 9)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы CentOS
images:
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# CentOS 7
- name: centos7-1
family: centos7
groups: [centos, test, web]
publish: ["7007:80"]
- name: centos7-2
family: centos7
groups: [centos, test, db]
publish: ["7008:80"]
# CentOS 8
- name: centos8-1
family: centos8
groups: [centos, test, web]
publish: ["7009:80"]
- name: centos8-2
family: centos8
groups: [centos, test, db]
publish: ["7010:80"]
# CentOS 9 Stream
- name: centos9-1
family: centos9
groups: [centos, test, web]
publish: ["7011:80"]
- name: centos9-2
family: centos9
groups: [centos, test, db]
publish: ["7012:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для CentOS 7
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ CentOS 7
images:
centos7: "inecs/ansible-lab:centos7-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# CentOS 7 хосты
- name: centos7-web
family: centos7
groups: [centos, test, web]
publish: ["7070:80"]
- name: centos7-db
family: centos7
groups: [centos, test, db]
publish: ["7071:80"]
- name: centos7-app
family: centos7
groups: [centos, test, app]
publish: ["7072:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для CentOS 8
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ CentOS 8
images:
centos8: "inecs/ansible-lab:centos8-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# CentOS 8 хосты
- name: centos8-web
family: centos8
groups: [centos, test, web]
publish: ["7080:80"]
- name: centos8-db
family: centos8
groups: [centos, test, db]
publish: ["7081:80"]
- name: centos8-app
family: centos8
groups: [centos, test, app]
publish: ["7082:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для CentOS 9 Stream
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ CentOS 9
images:
centos9: "inecs/ansible-lab:centos9-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# CentOS 9 Stream хосты
- name: centos9-web
family: centos9
groups: [centos, test, web]
publish: ["7090:80"]
- name: centos9-db
family: centos9
groups: [centos, test, db]
publish: ["7091:80"]
- name: centos9-app
family: centos9
groups: [centos, test, app]
publish: ["7092:80"]

View File

@@ -1,63 +0,0 @@
---
#description: Пресет со всеми версиями Debian (9, 10, 11, 12)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы Debian
images:
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian 9 Stretch
- name: debian9-1
family: debian9
groups: [debian, test, web]
publish: ["9009:80"]
- name: debian9-2
family: debian9
groups: [debian, test, db]
publish: ["9010:80"]
# Debian 10 Buster
- name: debian10-1
family: debian10
groups: [debian, test, web]
publish: ["9011:80"]
- name: debian10-2
family: debian10
groups: [debian, test, db]
publish: ["9012:80"]
# Debian 11 Bullseye
- name: debian11-1
family: debian11
groups: [debian, test, web]
publish: ["9013:80"]
- name: debian11-2
family: debian11
groups: [debian, test, db]
publish: ["9014:80"]
# Debian 12 Bookworm
- name: debian12-1
family: debian12
groups: [debian, test, web]
publish: ["9015:80"]
- name: debian12-2
family: debian12
groups: [debian, test, db]
publish: ["9016:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Debian 10 Buster
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Debian 10
images:
debian10: "inecs/ansible-lab:debian10-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian 10 Buster хосты
- name: debian10-web
family: debian10
groups: [debian, test, web]
publish: ["9100:80"]
- name: debian10-db
family: debian10
groups: [debian, test, db]
publish: ["9101:80"]
- name: debian10-app
family: debian10
groups: [debian, test, app]
publish: ["9102:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Debian 11 Bullseye
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Debian 11
images:
debian11: "inecs/ansible-lab:debian11-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian 11 Bullseye хосты
- name: debian11-web
family: debian11
groups: [debian, test, web]
publish: ["9110:80"]
- name: debian11-db
family: debian11
groups: [debian, test, db]
publish: ["9111:80"]
- name: debian11-app
family: debian11
groups: [debian, test, app]
publish: ["9112:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Debian 12 Bookworm
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Debian 12
images:
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian 12 Bookworm хосты
- name: debian12-web
family: debian12
groups: [debian, test, web]
publish: ["9120:80"]
- name: debian12-db
family: debian12
groups: [debian, test, db]
publish: ["9121:80"]
- name: debian12-app
family: debian12
groups: [debian, test, app]
publish: ["9122:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Debian 9 Stretch
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Debian 9
images:
debian9: "inecs/ansible-lab:debian9-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Debian 9 Stretch хосты
- name: debian9-web
family: debian9
groups: [debian, test, web]
publish: ["9090:80"]
- name: debian9-db
family: debian9
groups: [debian, test, db]
publish: ["9091:80"]
- name: debian9-app
family: debian9
groups: [debian, test, app]
publish: ["9092:80"]

View File

@@ -1,59 +0,0 @@
---
#description: Полный пресет с Docker функциональностью (DinD + DOoD)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Базовые хосты (стабильные ОС)
- name: u1
family: ubuntu22
groups: [test, web]
- name: u2
family: debian12
groups: [test, web]
# DinD узел (Docker-in-Docker)
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
# DOoD узел (Docker-out-of-Docker)
- name: dood1
type: dood
family: ubuntu22
groups: [dood]
publish: ["8081:8081"]
env:
DOCKER_HOST: unix:///var/run/docker.sock

View File

@@ -1,59 +0,0 @@
---
#description: Пресет с Docker контейнерами (DinD + DOoD) для тестирования Docker-задач
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Тестовые хосты
- name: test1
family: debian12
groups: [test]
- name: test2
family: rhel
groups: [test]
# DinD узел (Docker-in-Docker)
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
# DOoD узел (Docker-out-of-Docker)
- name: dood1
type: dood
family: debian12
groups: [dood]
publish: ["8081:8081"]
env:
DOCKER_HOST: unix:///var/run/docker.sock

View File

@@ -1,77 +0,0 @@
---
#description: Пресет для тестирования кластера etcd + PostgreSQL + Patroni (9 хостов)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Описание кластера etcd + Patroni + HAProxy
hosts:
# ETCD кластер (5 узлов для высокой доступности)
- name: etcd1
family: debian12
groups: [etcd, cluster]
- name: etcd2
family: rhel
groups: [etcd, cluster]
- name: etcd3
family: debian12
groups: [etcd, cluster]
- name: etcd4
family: rhel
groups: [etcd, cluster]
- name: etcd5
family: debian12
groups: [etcd, cluster]
# Patroni кластер (3 узла PostgreSQL)
- name: patroni1
family: rhel
groups: [patroni, database, cluster]
- name: patroni2
family: debian12
groups: [patroni, database, cluster]
- name: patroni3
family: rhel
groups: [patroni, database, cluster]
# HAProxy для балансировки
- name: haproxy
family: debian12
groups: [haproxy, loadbalancer]
publish: ["5000:5000", "5001:5001"] # RW и RO порты
# DinD узел для тестирования Docker Compose внутри
- name: app-dind
type: dind
groups: [apps, docker]
publish: ["8080:8080"]

View File

@@ -1,45 +0,0 @@
---
#description: Минимальный пресет для быстрого тестирования с 1 хостом (Debian)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Минимальный набор - один хост
- name: u1
family: astra
groups: [test]
supported_platforms: ["linux/amd64"] # Только amd64
- name: u2
family: alt
groups: [test]

View File

@@ -1,85 +0,0 @@
---
#description: Пресет для тестирования на разных ОС с 12 хостами (Debian + RHEL)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы для разных ОС
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Описание кластера с разными ОС
hosts:
# Debian серверы
- name: debian1
family: debian12
groups: [debian, servers, web]
- name: debian2
family: debian12
groups: [debian, servers, web]
- name: debian3
family: debian12
groups: [debian, servers, app]
- name: debian4
family: debian12
groups: [debian, servers, app]
# RHEL серверы
- name: rhel1
family: rhel
groups: [rhel, servers, web]
- name: rhel2
family: rhel
groups: [rhel, servers, web]
- name: rhel3
family: rhel
groups: [rhel, servers, app]
- name: rhel4
family: rhel
groups: [rhel, servers, app]
# База данных на разных ОС
- name: db-debian
family: debian12
groups: [database, debian, db]
- name: db-rhel
family: rhel
groups: [database, rhel, db]
# Load balancer
- name: lb-mixed
family: debian12
groups: [loadbalancer, haproxy]
publish: ["80:80", "443:443"]
# DinD узел для тестирования Docker
- name: docker-mixed
type: dind
groups: [docker, apps]
publish: ["8080:8080"]

View File

@@ -1,88 +0,0 @@
---
#description: Пресет для нагрузочного тестирования с 12 хостами (серверы + БД + кэш)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Описание кластера для нагрузочного тестирования
hosts:
# Основные серверы (5 узлов)
- name: server1
family: debian12
groups: [servers, web, app]
- name: server2
family: rhel
groups: [servers, web, app]
- name: server3
family: debian12
groups: [servers, web, app]
- name: server4
family: rhel
groups: [servers, web, app]
- name: server5
family: debian12
groups: [servers, web, app]
# База данных (3 узла)
- name: db1
family: rhel
groups: [database, db]
- name: db2
family: debian12
groups: [database, db]
- name: db3
family: rhel
groups: [database, db]
# Кэш (3 узла Redis)
- name: cache1
family: debian12
groups: [cache, redis]
- name: cache2
family: rhel
groups: [cache, redis]
- name: cache3
family: debian12
groups: [cache, redis]
# Load balancer
- name: lb1
family: rhel
groups: [loadbalancer, haproxy]
publish: ["80:80", "443:443"]
# DinD узел для тестирования Docker Compose
- name: compose-dind
type: dind
groups: [apps, docker]
publish: ["8080:8080", "8081:8081"]

View File

@@ -1,91 +0,0 @@
---
#description: Пресет для тестирования безопасности с 10 хостами (bastion + internal + monitoring)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Описание кластера для тестирования безопасности
hosts:
# Bastion хосты (точки входа)
- name: bastion1
family: rhel
groups: [bastion, security, jump]
publish: ["2222:22"]
- name: bastion2
family: debian12
groups: [bastion, security, jump]
publish: ["2223:22"]
# Внутренние серверы (без внешнего доступа)
- name: internal1
family: rhel
groups: [internal, servers, app]
- name: internal2
family: debian12
groups: [internal, servers, app]
- name: internal3
family: rhel
groups: [internal, servers, app]
# База данных (изолированная сеть)
- name: db-secure1
family: rhel
groups: [database, secure, internal]
- name: db-secure2
family: debian12
groups: [database, secure, internal]
# Мониторинг и логирование
- name: monitor1
family: debian12
groups: [monitoring, security, logs]
- name: monitor2
family: rhel
groups: [monitoring, security, logs]
# Firewall и сетевые компоненты
- name: fw1
family: rhel
groups: [firewall, network, security]
- name: fw2
family: debian12
groups: [firewall, network, security]
# DOoD узел для тестирования Docker безопасности
- name: docker-secure
type: dood
family: debian12
groups: [docker, security, apps]
publish: ["8080:8080"]
env:
DOCKER_HOST: "unix:///var/run/docker.sock"

View File

@@ -1,44 +0,0 @@
---
#description: Стабильный пресет для тестирования с Ubuntu и Debian (проверенные ОС)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Стабильные ОС для надежного тестирования
- name: u1
family: ubuntu22
groups: [test, web]
- name: u2
family: debian12
groups: [test, web]

View File

@@ -1,61 +0,0 @@
---
#description: Стандартный пресет для тестирования с 3 хостами (Debian + RHEL)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Kind кластеры (опционально)
# kind_clusters:
# - name: lab
# workers: 2
# api_port: 6443
# addons:
# ingress_nginx: true
# metrics_server: true
# istio: true
# kiali: true
# prometheus_stack: true
# ingress_host_http_port: 8081
# ingress_host_https_port: 8443
hosts:
# Стандартный набор - 3 хоста
- name: u1
family: debian12
groups: [test]
- name: u2
family: rhel
groups: [test]
- name: u3
family: debian12
groups: [test]

View File

@@ -1,41 +0,0 @@
---
#description: Минимальный пресет для быстрого тестирования с 1 хостом (Debian)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Минимальный набор - один хост
- name: u1
family: debian12
groups: [test]

View File

@@ -1,52 +0,0 @@
---
#description: Пресет со всеми версиями Ubuntu (20.04, 22.04, 24.04)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы Ubuntu
images:
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Ubuntu 20.04 LTS
- name: ubuntu20-1
family: ubuntu20
groups: [ubuntu, test, web]
publish: ["8020:80"]
- name: ubuntu20-2
family: ubuntu20
groups: [ubuntu, test, db]
publish: ["8026:80"]
# Ubuntu 22.04 LTS
- name: ubuntu22-1
family: ubuntu22
groups: [ubuntu, test, web]
publish: ["8022:80"]
- name: ubuntu22-2
family: ubuntu22
groups: [ubuntu, test, db]
publish: ["8023:80"]
# Ubuntu 24.04 LTS
- name: ubuntu24-1
family: ubuntu24
groups: [ubuntu, test, web]
publish: ["8024:80"]
- name: ubuntu24-2
family: ubuntu24
groups: [ubuntu, test, db]
publish: ["8025:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Ubuntu 20.04 LTS
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Ubuntu 20.04
images:
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Ubuntu 20.04 LTS хосты
- name: ubuntu20-web
family: ubuntu20
groups: [ubuntu, test, web]
publish: ["8020:80"]
- name: ubuntu20-db
family: ubuntu20
groups: [ubuntu, test, db]
publish: ["8021:80"]
- name: ubuntu20-app
family: ubuntu20
groups: [ubuntu, test, app]
publish: ["8022:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Ubuntu 22.04 LTS
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Ubuntu 22.04
images:
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Ubuntu 22.04 LTS хосты
- name: ubuntu22-web
family: ubuntu22
groups: [ubuntu, test, web]
publish: ["8220:80"]
- name: ubuntu22-db
family: ubuntu22
groups: [ubuntu, test, db]
publish: ["8221:80"]
- name: ubuntu22-app
family: ubuntu22
groups: [ubuntu, test, app]
publish: ["8222:80"]

View File

@@ -1,34 +0,0 @@
---
#description: Пресет для Ubuntu 24.04 LTS
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образ Ubuntu 24.04
images:
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Ubuntu 24.04 LTS хосты
- name: ubuntu24-web
family: ubuntu24
groups: [ubuntu, test, web]
publish: ["8240:80"]
- name: ubuntu24-db
family: ubuntu24
groups: [ubuntu, test, db]
publish: ["8241:80"]
- name: ubuntu24-app
family: ubuntu24
groups: [ubuntu, test, app]
publish: ["8242:80"]

View File

@@ -1,93 +0,0 @@
---
# Геополитический пресет с 5 контейнерами (Ubuntu + Debian + Alt + Astra + RedOS)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Описание: Пресет для тестирования ролей на различных ОС
# Использует образы Ubuntu, Debian, Alt, Astra и RedOS
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# Используем стабильные образы Ubuntu, Debian, Alt, Astra и RedOS (ARM64)
images:
ubuntu: "inecs/ansible-lab:ubuntu22-latest"
debian: "inecs/ansible-lab:debian12-latest"
alt: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-latest-arm64" # ARM64 образ
redos: "inecs/ansible-lab:redos-latest-arm64" # ARM64 образ
# Настройки для ARM64
# Используем нативные ARM64 образы
platform: "linux/arm64"
# Настройки Docker для ARM64
docker_options:
platform: "linux/arm64"
systemd_defaults:
privileged: true
command: "/bin/bash -c \"while true; do sleep 30; done\""
platform: "linux/arm64" # Используем ARM64
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Принудительная установка платформы для всех операций Docker
docker_platform: "linux/amd64"
hosts:
# =============================================================================
# UBUNTU СЕРВЕРЫ
# =============================================================================
- name: ubuntu1
family: ubuntu
groups: [geop, ubuntu]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# DEBIAN СЕРВЕРЫ
# =============================================================================
- name: debian1
family: debian
groups: [geop, debian]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# ALT СЕРВЕРЫ
# =============================================================================
- name: alt1
family: alt
groups: [geop, alt]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# ASTRA СЕРВЕРЫ (ARM64)
# =============================================================================
- name: astra1
family: astra
groups: [geop, astra]
platform: "linux/arm64" # Используем ARM64
docker_options:
platform: "linux/arm64"
docker_platform: "linux/arm64"
# =============================================================================
# REDOS СЕРВЕРЫ (ARM64)
# =============================================================================
#- name: redos1
# family: redos
# groups: [geop, redos]
# platform: "linux/arm64" # Используем ARM64
# docker_options:
# platform: "linux/arm64"
# docker_platform: "linux/arm64"

View File

@@ -1,42 +0,0 @@
---
#description: Минимальный Kind кластер без аддонов
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt: "inecs/ansible-lab:alt-linux-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Минимальный Kind кластер без аддонов
kind_clusters:
- name: minimal
workers: 0 # Только control-plane
api_port: 6443
hosts: []

View File

@@ -1,83 +0,0 @@
---
# ПРЕСЕТ: Kubernetes Multi-Cluster (3 кластера)
#
# Описание: Несколько Kind кластеров для тестирования мульти-кластерных сценариев
# - 3 Kind кластера: dev, staging, prod
# - Различные конфигурации для каждого окружения
# - Полный стек мониторинга и service mesh
#
# Использование: make lab-test SCENARIO=universal LAB_SPEC=molecule/presets/k8s-multi.yml
#
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
kind_clusters:
- name: dev
workers: 1
api_port: 6443
addons:
ingress_nginx: true
metrics_server: true
ingress_host_http_port: 8081
ingress_host_https_port: 8443
- name: staging
workers: 2
api_port: 6444
addons:
ingress_nginx: true
metrics_server: true
istio: true
kiali: true
ingress_host_http_port: 8082
ingress_host_https_port: 8444
- name: prod
workers: 3
api_port: 6445
addons:
ingress_nginx: true
metrics_server: true
istio: true
kiali: true
prometheus_stack: true
ingress_host_http_port: 8083
ingress_host_https_port: 8445
images:
debian: "inecs/ansible-lab:ubuntu22-latest"
ubuntu: "inecs/ansible-lab:ubuntu22-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos: "inecs/ansible-lab:centos9-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs:
- "/run"
- "/run/lock"
capabilities:
- "SYS_ADMIN"
hosts:
- name: k8s-dev-controller
group: controllers
family: debian
publish:
- "6443:6443"
- name: k8s-staging-controller
group: controllers
family: rhel
publish:
- "6444:6444"
- name: k8s-prod-controller
group: controllers
family: debian
publish:
- "6445:6445"

View File

@@ -1,42 +0,0 @@
---
#description: Минимальный пресет для быстрого тестирования с 1 хостом (Debian)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Минимальный набор - один хост Astra Linux для arm64
- name: u1
family: astra
groups: [test]
supported_platforms: ["linux/arm64", "linux/amd64"]

View File

@@ -1,47 +0,0 @@
---
#description: Стандартный пресет по умолчанию для тестирования с 2 хостами (Ubuntu + Debian)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
alt9: "inecs/ansible-lab:alt9-latest"
alt10: "inecs/ansible-lab:alt10-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos7: "inecs/ansible-lab:centos7-latest"
centos8: "inecs/ansible-lab:centos8-latest"
centos9: "inecs/ansible-lab:centos9-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu20: "inecs/ansible-lab:ubuntu20-latest"
ubuntu22: "inecs/ansible-lab:ubuntu22-latest"
ubuntu24: "inecs/ansible-lab:ubuntu24-latest"
debian9: "inecs/ansible-lab:debian9-latest"
debian10: "inecs/ansible-lab:debian10-latest"
debian11: "inecs/ansible-lab:debian11-latest"
debian12: "inecs/ansible-lab:debian12-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Стандартный набор - 2 хоста для базового тестирования (стабильные ОС)
- name: u1
family: ubuntu22
groups: [test]
- name: u2
family: debian12
groups: [test]
- name: u3
family: centos9
groups: [test]

View File

@@ -1,248 +0,0 @@
# Роль devops
Универсальная роль для создания пользователя devops с SSH ключами и sudo правами на всех поддерживаемых операционных системах.
## Автор
**Сергей Антропов**
Сайт: https://devops.org.ru
## Описание
Роль `devops` создает универсального пользователя для DevOps задач со следующими возможностями:
- ✅ Создание пользователя `devops` с настраиваемым UID/GID
- ✅ Установка пароля длиной 30 символов из vault
- ✅ Настройка sudo прав без ввода пароля
- ✅ Добавление SSH публичного ключа для подключения
- ✅ Поддержка всех ОС из dockerfiles/
- ✅ Детальное логирование и проверки
- ✅ Уведомления о статусе выполнения
## Поддерживаемые ОС
- **Red Hat семейство**: RHEL 7/8/9, CentOS 7/8/9, AlmaLinux 8, Rocky Linux 8
- **Debian семейство**: Debian 9/10/11/12, Ubuntu 20.04/22.04/24.04
- **SUSE**: SLES 15.x
- **Alpine**: 3.15+
- **Российские ОС**: Astra Linux 1.7, RED OS 7/9, ALT Linux 9/10
## Требования
- Ansible >= 2.9
- Python >= 3.6
- Доступ к vault с секретами
- Права sudo/root на целевых хостах
## Переменные
### Основные переменные
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `devops_user` | `devops` | Имя пользователя |
| `devops_group` | `devops` | Основная группа пользователя |
| `devops_home` | `/home/devops` | Домашняя директория |
| `devops_shell` | `/bin/bash` | Оболочка пользователя |
| `devops_uid` | `1001` | UID пользователя |
| `devops_gid` | `1001` | GID группы |
### Переменные из vault
| Переменная | Описание | Обязательная |
|------------|----------|--------------|
| `vault_devops_password` | Пароль пользователя (30 символов) | ✅ |
| `vault_devops_ssh_public_key` | SSH публичный ключ | ✅ |
### Настройки sudo
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `devops_sudo_nopasswd` | `true` | Выполнение sudo без пароля |
| `devops_sudo_commands` | `ALL` | Разрешенные команды |
| `devops_sudoers_file` | `/etc/sudoers.d/devops` | Путь к файлу sudoers |
### Настройки SSH
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `devops_ssh_dir` | `/home/devops/.ssh` | SSH директория |
| `devops_ssh_authorized_keys` | `/home/devops/.ssh/authorized_keys` | Файл с ключами |
| `devops_ssh_dir_mode` | `0700` | Права на SSH директорию |
| `devops_ssh_keys_mode` | `0600` | Права на файлы ключей |
### Дополнительные группы
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `devops_additional_groups` | `['sudo', 'wheel', 'docker', 'systemd-journal']` | Дополнительные группы |
## Примеры использования
### Базовое использование
```yaml
- hosts: all
become: yes
roles:
- devops
vars:
vault_devops_password: "your-30-char-password-here"
vault_devops_ssh_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC..."
```
### С кастомными настройками
```yaml
- hosts: all
become: yes
roles:
- devops
vars:
devops_user: "admin"
devops_home: "/home/admin"
devops_uid: 2000
devops_gid: 2000
devops_additional_groups:
- "sudo"
- "docker"
- "kvm"
vault_devops_password: "your-30-char-password-here"
vault_devops_ssh_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC..."
```
### С отключенными проверками
```yaml
- hosts: all
become: yes
roles:
- devops
vars:
devops_verify_user: false
devops_verify_ssh: false
devops_verify_sudo: false
devops_notify_on_success: false
vault_devops_password: "your-30-char-password-here"
vault_devops_ssh_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC..."
```
## Теги
Роль поддерживает следующие теги для выборочного выполнения:
- `devops` - все задачи роли
- `validation` - проверка входных параметров
- `packages` - установка пакетов
- `group` - создание группы
- `user` - создание пользователя
- `password` - установка пароля
- `ssh` - настройка SSH
- `keys` - настройка SSH ключей
- `sudo` - настройка sudo прав
- `permissions` - настройка прав доступа
- `config` - дополнительная конфигурация
- `verification` - проверка настройки
- `notification` - уведомления
### Примеры использования тегов
```bash
# Только создание пользователя и группы
ansible-playbook -i inventory converge.yml --tags "user,group"
# Только настройка SSH
ansible-playbook -i inventory converge.yml --tags "ssh,keys"
# Только настройка sudo
ansible-playbook -i inventory converge.yml --tags "sudo,permissions"
# Пропустить проверки
ansible-playbook -i inventory converge.yml --skip-tags "verification"
```
## Обработчики
Роль включает следующие обработчики:
- `restart ssh service` - перезапуск SSH сервиса
- `check ssh config` - проверка конфигурации SSH
- `log changes` - логирование изменений
- `notify completion` - уведомления о завершении
- `cleanup temp files` - очистка временных файлов
- `security check` - проверка безопасности
- `collect statistics` - сбор статистики
## Логирование
Роль поддерживает детальное логирование:
- Уровень логирования настраивается через `devops_log_level`
- Логи записываются в `devops_log_file` (по умолчанию `/var/log/devops-setup.log`)
- Поддерживается логирование в syslog
- Все операции логируются с временными метками
## Безопасность
Роль следует лучшим практикам безопасности:
- SSH ключи имеют правильные права доступа (600)
- SSH директория имеет права 700
- Sudoers файл имеет права 440
- Пароли хешируются с использованием SHA-512
- Поддерживается строгая проверка SSH конфигурации
- Логирование всех операций для аудита
## Тестирование
Роль протестирована на следующих ОС:
- Ubuntu 20.04/22.04/24.04
- Debian 9/10/11/12
- CentOS 7/8/9
- RHEL 7/8/9
- AlmaLinux 8
- Rocky Linux 8
- SLES 15.x
- Alpine 3.15+
- Astra Linux 1.7
- RED OS 7/9
- ALT Linux 9/10
## Устранение неполадок
### Проблемы с паролем
```bash
# Проверка хеша пароля
ansible all -m debug -a "msg={{ 'your-password' | password_hash('sha512') }}"
```
### Проблемы с SSH
```bash
# Проверка SSH конфигурации
sudo sshd -t
# Проверка прав доступа
ls -la /home/devops/.ssh/
```
### Проблемы с sudo
```bash
# Проверка sudoers файла
sudo visudo -c -f /etc/sudoers.d/devops
# Тест sudo прав
sudo -l -U devops
```
## Лицензия
MIT
## Поддержка
- Сайт: https://devops.org.ru
- Автор: Сергей Антропов

View File

@@ -1,74 +0,0 @@
---
# Переменные по умолчанию для роли devops
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Основные настройки пользователя devops
devops_user: "devops"
devops_group: "devops"
devops_home: "/home/{{ devops_user }}"
devops_shell: "/bin/bash"
# Настройки пароля (обязательно из vault/secrets.yml)
devops_password: "{{ vault_devops_password }}"
# Настройки SSH ключа (обязательно из vault/secrets.yml)
devops_ssh_public_key: "{{ vault_devops_ssh_public_key }}"
# Настройки sudo
devops_sudo_nopasswd: true
devops_sudo_commands: "ALL"
# Дополнительные группы для пользователя
devops_additional_groups:
- "sudo"
- "wheel"
- "docker"
- "systemd-journal"
# Настройки SSH
devops_ssh_dir: "{{ devops_home }}/.ssh"
devops_ssh_authorized_keys: "{{ devops_ssh_dir }}/authorized_keys"
devops_ssh_dir_mode: "0700"
devops_ssh_keys_mode: "0600"
# Настройки безопасности
devops_umask: "0022"
devops_uid: 1001
devops_gid: 1001
# Настройки логирования
devops_log_level: "info"
devops_log_file: "/var/log/devops-setup.log"
# Настройки для разных ОС
devops_package_manager:
redhat: "yum"
debian: "apt"
suse: "zypper"
alpine: "apk"
# Список пакетов для установки (если нужно)
devops_packages: []
# Настройки для создания пользователя
devops_create_home: true
devops_system_user: false
devops_login_shell: true
# Настройки для sudoers
devops_sudoers_file: "/etc/sudoers.d/{{ devops_user }}"
devops_sudoers_template: "devops_sudoers.j2"
# Настройки для SSH
devops_ssh_config: "{{ devops_home }}/.ssh/config"
devops_ssh_known_hosts: "{{ devops_home }}/.ssh/known_hosts"
# Настройки для проверки
devops_verify_user: true
devops_verify_ssh: true
devops_verify_sudo: true
# Настройки для уведомлений
devops_notify_on_success: true
devops_notify_on_failure: true

View File

@@ -1,161 +0,0 @@
---
# Обработчики для роли devops
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: "🔄 Перезапуск SSH сервиса"
block:
- name: "Перезапуск SSH сервиса (systemd)"
listen: "restart ssh service"
systemd:
name: "{{ item }}"
state: restarted
enabled: yes
daemon_reload: yes
become: true
loop: "{{ devops_services_to_enable }}"
when: ansible_service_mgr == "systemd"
- name: "Перезапуск SSH сервиса (service)"
service:
name: "{{ item }}"
state: restarted
enabled: yes
become: true
loop: "{{ devops_services_to_enable }}"
when: ansible_service_mgr == "service"
- name: "Перезапуск SSH сервиса (rc-service)"
service:
name: "{{ item }}"
state: restarted
enabled: yes
become: true
loop: "{{ devops_services_to_enable }}"
when: ansible_service_mgr == "rc-service"
- name: "🔍 Проверка конфигурации SSH"
block:
- name: "Проверка конфигурации SSH сервера"
listen: "check ssh config"
command: "sshd -t"
become: true
register: sshd_config_check
changed_when: false
failed_when: sshd_config_check.rc != 0
- name: "Логирование проверки SSH конфигурации"
debug:
msg: "SSH конфигурация проверена успешно"
when: false
- name: "📝 Логирование изменений"
block:
- name: "Запись в лог файл"
listen: "log changes"
lineinfile:
path: "{{ devops_log_file }}"
line: "{{ ansible_date_time.iso8601 }} - {{ ansible_hostname }} - {{ ansible_user_id }} - {{ ansible_play_name }} - {{ ansible_task_name }}"
create: yes
owner: root
group: root
mode: '0644'
become: true
when: devops_log_file is defined
- name: "Логирование в syslog"
syslogger:
facility: "local0"
priority: "info"
msg: "DevOps role: {{ ansible_task_name }} completed on {{ ansible_hostname }}"
when: false
- name: "🔔 Уведомления о завершении"
block:
- name: "Уведомление об успешном завершении"
listen: "notify completion"
debug:
msg: "✅ Роль devops успешно выполнена на {{ ansible_hostname }}"
when: devops_notify_on_success
- name: "Уведомление об ошибке"
debug:
msg: "❌ Ошибка при выполнении роли devops на {{ ansible_hostname }}"
when: devops_notify_on_failure
- name: "🧹 Очистка временных файлов"
block:
- name: "Удаление временных файлов"
listen: "cleanup temp files"
file:
path: "{{ item }}"
state: absent
become: true
loop:
- "/tmp/devops_setup_*"
- "/tmp/ansible_*"
ignore_errors: true
- name: "Логирование очистки"
debug:
msg: "Временные файлы очищены"
when: false
- name: "🔐 Проверка безопасности"
block:
- name: "Проверка прав доступа к файлам пользователя"
listen: "security check"
stat:
path: "{{ devops_home }}"
register: home_dir_check
- name: "Проверка прав доступа к SSH директории"
stat:
path: "{{ devops_ssh_dir }}"
register: ssh_dir_check
- name: "Проверка прав доступа к authorized_keys"
stat:
path: "{{ devops_ssh_authorized_keys }}"
register: ssh_keys_check
- name: "Проверка прав доступа к sudoers файлу"
stat:
path: "{{ devops_sudoers_file }}"
register: sudoers_file_check
- name: "Логирование проверки безопасности"
debug:
msg: |
Результаты проверки безопасности:
- Домашняя директория: {{ home_dir_check.stat.exists }}
- SSH директория: {{ ssh_dir_check.stat.exists }}
- SSH ключи: {{ ssh_keys_check.stat.exists }}
- Sudoers файл: {{ sudoers_file_check.stat.exists }}
- name: "📊 Сбор статистики"
block:
- name: "Сбор информации о пользователе"
listen: "collect statistics"
command: "id {{ devops_user }}"
register: user_info
changed_when: false
- name: "Сбор информации о группах пользователя"
command: "groups {{ devops_user }}"
register: user_groups
changed_when: false
- name: "Сбор информации о SSH ключах"
command: "wc -l {{ devops_ssh_authorized_keys }}"
register: ssh_keys_count
changed_when: false
ignore_errors: true
- name: "Логирование статистики"
debug:
msg: |
Статистика пользователя {{ devops_user }}:
- Информация: {{ user_info.stdout }}
- Группы: {{ user_groups.stdout }}
- SSH ключей: {{ ssh_keys_count.stdout | default('неизвестно') }}

View File

@@ -1,67 +0,0 @@
---
# Метаданные роли devops
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
galaxy_info:
author: "Сергей Антропов"
description: "Универсальная роль для создания пользователя devops с SSH ключами и sudo правами"
company: "DevOpsLab"
license: "MIT"
min_ansible_version: "2.9"
platforms:
- name: "EL"
versions:
- "7"
- "8"
- "9"
- name: "Ubuntu"
versions:
- "focal"
- "jammy"
- "noble"
- name: "Debian"
versions:
- "stretch"
- "buster"
- "bullseye"
- "bookworm"
- name: "SLES"
versions:
- "15"
- "15SP1"
- "15SP2"
- "15SP3"
- "15SP4"
- "15SP5"
- name: "Alpine"
versions:
- "all"
- name: "Astra Linux"
versions:
- "all"
- name: "EL"
versions:
- "7"
- "8"
- "9"
- name: "EL"
versions:
- "7"
- "8"
- "9"
galaxy_tags:
- "user"
- "devops"
- "ssh"
- "sudo"
- "security"
- "system"
- "administration"
- "automation"
- "universal"
- "crossplatform"
dependencies: []
collections: []

View File

@@ -1,312 +0,0 @@
---
# Основные задачи для роли devops
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: "Reset ANSI color codes"
debug:
msg: "\033[0m"
changed_when: false
tags: [devops, color-reset]
- name: "🔍 Проверка входных параметров"
tags: [devops, validation]
block:
- name: "Проверка наличия пароля пользователя devops"
fail:
msg: |
❌ ОШИБКА: Пароль пользователя devops не определён.
Ожидаемая переменная: vault_devops_password
Где задавать: roles/devops/vars/main.yml (или переопределить на уровне инвентаря/группы/хоста)
Пример в roles/devops/vars/main.yml:
vault_devops_password: "ваш_пароль_здесь"
when:
- vault_devops_password is not defined
- vault_devops_password == ""
- vault_devops_password is none
- name: "Проверка наличия SSH публичного ключа"
fail:
msg: |
❌ ОШИБКА: SSH публичный ключ не определён.
Ожидаемая переменная: vault_devops_ssh_public_key
Где задавать: roles/devops/vars/main.yml (или переопределить на уровне инвентаря/группы/хоста)
Пример в roles/devops/vars/main.yml:
vault_devops_ssh_public_key: |
ssh-rsa AAAAB3... user@host
when:
- vault_devops_ssh_public_key is not defined
- vault_devops_ssh_public_key == ""
- vault_devops_ssh_public_key is none
- name: "✅ Проверка успешна - все необходимые секреты найдены"
debug:
msg: |
Все необходимые секреты найдены в переменных роли (vars/main.yml или инвентарь):
- Пароль пользователя: {{ 'установлен' if vault_devops_password is defined and vault_devops_password != '' else 'НЕ УСТАНОВЛЕН' }}
- SSH публичный ключ: {{ 'установлен' if vault_devops_ssh_public_key is defined and vault_devops_ssh_public_key != '' else 'НЕ УСТАНОВЛЕН' }}
when:
- vault_devops_password is defined
- vault_devops_password != ""
- vault_devops_ssh_public_key is defined
- vault_devops_ssh_public_key != ""
- name: "🔍 Проверка что пароль не пустой"
fail:
msg: "❌ ОШИБКА: Пароль пользователя devops не может быть пустым!"
when:
- vault_devops_password is defined
- vault_devops_password == ""
- name: "🔍 Проверка что SSH ключ не пустой"
fail:
msg: "❌ ОШИБКА: SSH публичный ключ не может быть пустым!"
when:
- vault_devops_ssh_public_key is defined
- vault_devops_ssh_public_key == ""
- name: "Логирование начала выполнения роли"
debug:
msg: "Начинаем настройку пользователя {{ devops_user }} на {{ ansible_distribution }} {{ ansible_distribution_version }}"
when: false
- name: "📦 Установка необходимых пакетов"
tags: [devops, packages]
block:
- name: "Обновление кеша пакетов (Debian/Ubuntu)"
apt:
update_cache: yes
cache_valid_time: 3600
when: devops_os_family == "debian"
- name: "Установка необходимых пакетов"
package:
name: "{{ devops_packages_to_install }}"
state: present
become: true
- name: "👤 Создание группы devops"
tags: [devops, group]
block:
- name: "Проверка существования группы {{ devops_group }}"
group:
name: "{{ devops_group }}"
state: present
gid: "{{ devops_gid }}"
become: true
register: group_create_result
- name: "Логирование создания группы"
debug:
msg: "Группа {{ devops_group }} создана/существует"
when: false
- name: "👥 Создание недостающих групп"
tags: [devops, groups, additional]
block:
- name: "Создание группы wheel (если не существует)"
group:
name: wheel
state: present
become: true
when: "'wheel' in devops_final_additional_groups"
ignore_errors: true
- name: "Создание группы sudo (если не существует)"
group:
name: sudo
state: present
become: true
when: "'sudo' in devops_final_additional_groups"
ignore_errors: true
- name: "Создание группы systemd-journal (если не существует)"
group:
name: systemd-journal
state: present
become: true
when: "'systemd-journal' in devops_final_additional_groups"
ignore_errors: true
- name: "Создание группы docker (если не существует)"
group:
name: docker
state: present
become: true
when: "'docker' in devops_final_additional_groups"
ignore_errors: true
- name: "👤 Создание пользователя devops"
tags: [devops, user, password]
block:
- name: "Проверка существования пользователя {{ devops_user }}"
user:
name: "{{ devops_user }}"
group: "{{ devops_group }}"
uid: "{{ devops_uid }}"
home: "{{ devops_home }}"
shell: "{{ devops_shell }}"
state: present
create_home: "{{ devops_create_home }}"
system: "{{ devops_system_user }}"
groups: "{{ devops_final_additional_groups }}"
append: yes
become: true
register: user_create_result
- name: "Установка пароля для пользователя {{ devops_user }}"
user:
name: "{{ devops_user }}"
password: "{{ devops_password | password_hash('sha512') }}"
update_password: always
become: true
no_log: true
register: password_set_result
- name: "Логирование создания пользователя"
debug:
msg: "Пользователь {{ devops_user }} создан/обновлен с паролем"
when: false
- name: "🔑 Настройка SSH ключей"
tags: [devops, ssh, keys]
block:
- name: "Создание директории .ssh для пользователя {{ devops_user }}"
file:
path: "{{ devops_ssh_dir }}"
state: directory
owner: "{{ devops_user }}"
group: "{{ devops_group }}"
mode: "{{ devops_ssh_dir_mode }}"
become: true
- name: "Добавление SSH публичного ключа в authorized_keys"
authorized_key:
user: "{{ devops_user }}"
key: "{{ devops_ssh_public_key }}"
state: present
manage_dir: no
become: true
register: ssh_key_result
when: devops_ssh_public_key != ""
- name: "Установка правильных прав на authorized_keys"
file:
path: "{{ devops_ssh_authorized_keys }}"
owner: "{{ devops_user }}"
group: "{{ devops_group }}"
mode: "{{ devops_ssh_keys_mode }}"
become: true
when: devops_ssh_public_key != ""
- name: "Логирование настройки SSH"
debug:
msg: "SSH ключ для пользователя {{ devops_user }} настроен"
when: false
- name: "🔐 Настройка sudo прав"
tags: [devops, sudo, permissions]
block:
- name: "Создание файла sudoers для пользователя {{ devops_user }}"
template:
src: "{{ devops_sudoers_template }}"
dest: "{{ devops_sudoers_file }}"
owner: root
group: root
mode: '0440'
backup: yes
become: true
register: sudoers_result
- name: "Проверка синтаксиса sudoers файла"
command: "visudo -c -f {{ devops_sudoers_file }}"
become: true
register: sudoers_check
changed_when: false
failed_when: sudoers_check.rc != 0
- name: "Логирование настройки sudo"
debug:
msg: "Sudo права для пользователя {{ devops_user }} настроены"
when: false
- name: "🔧 Настройка дополнительных параметров"
tags: [devops, config, additional]
block:
- name: "Установка umask для пользователя {{ devops_user }}"
lineinfile:
path: "{{ devops_home }}/.bashrc"
line: "umask {{ devops_umask }}"
owner: "{{ devops_user }}"
group: "{{ devops_group }}"
mode: '0644'
create: yes
backup: yes
become: true
- name: "Создание SSH конфигурации для пользователя {{ devops_user }}"
template:
src: "devops_ssh_config.j2"
dest: "{{ devops_ssh_config }}"
owner: "{{ devops_user }}"
group: "{{ devops_group }}"
mode: "0600"
backup: yes
become: true
- name: "Логирование дополнительных настроек"
debug:
msg: "Дополнительные настройки для пользователя {{ devops_user }} применены"
when: false
- name: "✅ Проверка настройки пользователя devops"
when: devops_verify_user
tags: [devops, verification, check]
block:
- name: "Проверка существования пользователя {{ devops_user }}"
command: "id {{ devops_user }}"
register: user_check
changed_when: false
failed_when: user_check.rc != 0
- name: "Проверка SSH ключа пользователя {{ devops_user }}"
stat:
path: "{{ devops_ssh_authorized_keys }}"
register: ssh_key_check
- name: "Проверка sudo прав пользователя {{ devops_user }}"
command: "sudo -l -U {{ devops_user }}"
become: true
register: sudo_check
changed_when: false
failed_when: sudo_check.rc != 0
- name: "Логирование результатов проверки"
debug:
msg: |
Результаты проверки пользователя {{ devops_user }}:
- Пользователь существует: {{ user_check.rc == 0 }}
- SSH ключ настроен: {{ ssh_key_check.stat.exists }}
- Sudo права настроены: {{ sudo_check.rc == 0 }}
- name: "📝 Уведомления о завершении"
tags: [devops, notification, success]
block:
- name: "Уведомление об успешном завершении"
debug:
msg: "✅ Пользователь {{ devops_user }} успешно настроен на {{ ansible_hostname }}"
when: devops_notify_on_success
- name: "Уведомление о настройке SSH"
debug:
msg: "🔑 SSH ключ для пользователя {{ devops_user }} настроен"
when: devops_notify_on_success and devops_verify_ssh
- name: "Уведомление о настройке sudo"
debug:
msg: "🔐 Sudo права для пользователя {{ devops_user }} настроены"
when: devops_notify_on_success and devops_verify_sudo

View File

@@ -1,79 +0,0 @@
# SSH конфигурация для пользователя {{ devops_user }}
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Создан: {{ ansible_date_time.iso8601 }}
# Основные настройки SSH клиента
Host *
# Настройки безопасности
StrictHostKeyChecking ask
UserKnownHostsFile ~/.ssh/known_hosts
IdentitiesOnly yes
# Настройки подключения
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
# Настройки сжатия
Compression yes
CompressionLevel 6
# Настройки шифрования
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
# Настройки MAC
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512
# Настройки KEX
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521
# Настройки аутентификации
PreferredAuthentications publickey,password
PubkeyAuthentication yes
PasswordAuthentication yes
# Настройки портов
Port 22
# Настройки таймаутов
ConnectTimeout 30
ConnectionAttempts 3
# Настройки логирования
LogLevel INFO
# Настройки для X11 forwarding (если нужно)
# ForwardX11 yes
# ForwardX11Trusted yes
# Настройки для агента SSH
ForwardAgent yes
# Настройки для туннелирования
# LocalForward 8080 localhost:80
# RemoteForward 9090 localhost:9090
# Специфичные настройки для разных хостов
# Host production
# HostName prod.example.com
# User {{ devops_user }}
# Port 2222
# IdentityFile ~/.ssh/id_rsa_prod
# StrictHostKeyChecking yes
# UserKnownHostsFile ~/.ssh/known_hosts_prod
# Host staging
# HostName staging.example.com
# User {{ devops_user }}
# Port 22
# IdentityFile ~/.ssh/id_rsa_staging
# StrictHostKeyChecking no
# Host development
# HostName dev.example.com
# User {{ devops_user }}
# Port 22
# IdentityFile ~/.ssh/id_rsa_dev
# StrictHostKeyChecking no
# UserKnownHostsFile /dev/null

View File

@@ -1,21 +0,0 @@
# Sudoers файл для пользователя {{ devops_user }}
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Создан: {{ ansible_date_time.iso8601 }}
# Разрешить пользователю {{ devops_user }} выполнять все команды без ввода пароля
{{ devops_user }} ALL=(ALL) NOPASSWD:ALL
# Дополнительные настройки безопасности
# Разрешить выполнение команд только от определенных терминалов (опционально)
# {{ devops_user }} ALL=(ALL) NOPASSWD:ALL, !/usr/bin/passwd, !/usr/bin/su
# Логирование всех команд sudo (опционально)
# Defaults logfile=/var/log/sudo.log
# Defaults log_input, log_output
# Настройки таймаута для sudo сессий (опционально)
# Defaults timestamp_timeout=15
# Разрешить выполнение команд без подтверждения для определенных команд
# {{ devops_user }} ALL=(ALL) NOPASSWD: /bin/systemctl, /usr/bin/docker, /usr/bin/kubectl

View File

@@ -1,386 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
30393164313831366361633763646433613761663339323630623464646262323739346263643965
3836316437636537656631656239323532336632386634660a303662373864313034313131366364
30623732373861323839653763393637383935333633646634393762613536386136633162656231
3462323037353437370a623838343533633633643961303761383265373062633161636562386138
33393465626363633538376161393065303766396438356138633763353962613435323030656561
66663634633536626338343763333861613562303866646365663365333738663139303230386531
65653235626537623732653431306539376433396434373530643035666564333131303737383362
38393133656137646238633538366337383961363839383162653731613232643638396134613433
37663739646663363832313465613335396432643265313964396235316631626135393231306465
62336433623630613139653530383836333566373038353035353161623162343235633034636430
62366436353965363032313630353432656434326362303938326362643134386136616431636433
39363135656561623335303936326637373066316434356534383338313739663633643938653739
61366432656262343838646139373632666438666164623830643333393563336435643762326461
62313639613633373837646538303061386632626330333739643864316166363463383235336166
39363938613434663131323466643063646431663463323636353035353630343563623233376630
63636636386565343630613438663165326237616137316333303834396337613261633765316566
36613532336234356138366330666433353764646266633564326634386263336235646335363561
63386635326664343837396539396530306230633130616664613666373530613236356631303432
39323162653135633161343331626539613866323030653030633832373536333834373365613765
36343964313539383430333761663132613530376135336561396532626330646262373462663661
31363962643161313534303566613764336633653138636631323766656361363336353062623532
36316534636136643362393432346239346463643061356438363934323937643961376232386439
63316466363234313338303830613532353238356331343134646365353637373462626662373834
65626266303966626465373365356334613039376334623764366566343964376562386336373031
66653030646531653265666138643936653033343663323365656538623764373237373638663233
30623039623866653761393264323930613866343861323934373834343032646531666439336334
32383932323464313332616139363032623061633663336130636531613036643631653032303237
64643564373533393630643364663733386662393165636635363331353033333963623130633931
61626338353937376266666165333433396639666630626166656231633062353933366639393563
31326465626531363539393861383430633530386536356336396664383637386366363731346561
33363330366635666330666265306230373462323136333935336335346238613734326464303036
64303764396265306132663735333732643337623430353539313539336339346664353461653566
35353639363938333835653961613861383334663263643532396333383738306162386663363632
61343833643139653537333665663963363565646432333939366332646364326261306237643631
33396235336134633833343561383662353133623431316663376162613463336633393963356132
62636561623838626133336563393063376662313935646464663730636536346337613565313965
30386135643132633130383330643866303436663033306362613863343939646337343733376638
35626562363664383063666333653463626162353661376462636633343534653461333738303636
66663764643465363765626336623163326161306462316561373964633461353066636631616130
64333536646362626533346462633833333761633366613362623666333639383438333431343934
39333766376563646334663638363561343766373763353761663664623931393437343864306638
38623561343064633830653533373365336136333130356434323731376563373835623461626432
30363562613235303634383766623836303361626131323734363333653435353436363137333639
35623631343838333936316466323666353863316461353063326264613763653537323765663131
37306231643138353834623065623735386133333531663762303436366531363639303131393263
36666661373965636339633162343537663236666463656236353838363837666533376232636564
36383532663865323162613962313639643265386231623231353934656261666235313132313966
66626637356632396135306630653463633436363731643031623864613833353137653762383538
37646631373231626539323333663234613563393332336465333266353763313432616337653933
39633234663137623037336639303133353764656438663438393964643135666236643333303238
64313732376430626535663534363138363434313264666663366338383837363734363739333866
31383734326166663466326137336364613964326239653134346532376636363230353964373132
36666135303935366436343131653666616233663033656634306162346166653763353038376264
38323066376333633332616636613662323335386537653162343439303466363234306232623535
31316635616132336366333462336161366262376663376164333231633538323463323539626664
30303334363762353636363837366163643237373763343137373463623832373538373738656630
63323564616434313930623462356462323566663932386439323866313437313937306230643437
65643331343838363466333061303632363131363661656232643364303339333731373932633638
63643232613232663132373062333530316433323938396336363164623761653835633666363938
31393636663566663031613061613736386364363130623265313434383931323465633533316638
65303839373737663339636362623462373533373633386362306238373130666462623534373862
63376631316637356464633438643863323735363434396131396165303733373165623331386238
66343532313235616364323564393931376439666237333466373964383634616338653634333062
64623239633630626235643765336565396662336265376538663161636464646365386162663937
37373531623463623830393436616266613330363731616234373338323732333737633563376436
34636436383632623333613230333434613731393966633738623635376630616434663661663936
33346632353137663238643731363766316462616362343161316637646161373262346533666461
32353836633437613732333038343837333637323131336363376439646535363933376133313738
39626138663338653636616634356437646139633766653730633437333765626365303564653865
35666432623331653832383064623232656462343837663934626363653734643162653061663162
64303034356530376661616562646239663061613939663666366165363733626635623938336163
34383033326531323164656132383161623439343236356236363362303961653837616261363337
64623138656434356166646238356463306531656661306566376565303533316635353337353263
32383137616163666362613137366162383338663439373334393235323562393862666162373463
37343461303430303838326435316233366364613662373437333039313835343033383837323339
33656231306666393131396235363761353933643639306532623338356131366233333237613732
61326161396137626438633833626435643365303534643963636566366631336131306536643931
62646439613465336436633539633461633833633661353637643634643765653638306338626336
64373862333134323561323337656562353634336331373563666433636539396431333238323330
63323636623061393666623433353937346537393966623438343061383638323631393333333863
37306366366236663565633133303766643035346165643635363962326536393531633236326565
37646638303139636435663133376336333230336239326666323664386630613230666666333137
65376339633763326136353031313034333835363538306532623339643163303436653730656636
38333735353037373064613962663236643461376336613934646463356165626661346662336266
61306262363835623062613561393431643133613462643334396336363238633731396538643034
62633632373962646162623337373561356163393936333635636233636166643339613165346231
39323031316363656539613732373833353334356265376662383538656532333639303435623763
36666363313064623734633564613734353034393366376166653463636335623431663962376436
36373866623631363737623939396563636336636334393763333163333066643331326563333532
35326436646139346430633264396638303766663237396630383439343266363164316435613637
32323838636632626461373439306437326432383864386561336338633866643639313230663737
65616365316162336661396234323735343766303031396236613363633864356531333531353235
33373865366332336432353130386333313335313239333339396232356436643034353830323036
61613362376265643131613636393831393932653965383261303733653734396536343630333436
64613834616236623534393665383164373934303163663163313231653937306436363865386637
65666136326161626238313334373237363731333532346364346464633265643730666530636534
34623031346439373033346434666466646137623765396435363561363263666566393433303636
38323736303466383739373336646664343563333561623938346337303331343132323166373338
37396536356530633263303666356439326562393535646139323666343632366636353365343230
37626130396238313132633065633864373739626433333832336130613661313838353239303239
39633435313966646665313065643230623231626266326536383664616463353664653562333236
34373965326639363730386262643931666138653530396565656665363239656537356361616562
32343435636466653634383365343364373030633731323265383438646339393466396236613638
63643466323061656264323065323533653265336261396435343036383136343436346231663933
32303437343264363866663639653331303731653163613636616333323462346261376364343565
35336431393632636464353463613464393633373335366232646262353532616165613334663737
63623962636637626133643532363230343263613937623430363462633038616665333262636230
37346336356262653132663330646339353964353637653865633566386233623536336234336265
63326563336265343131396466313430323937653039376136643238323037346538633139386231
39653236306365363665343333326331643464393934656663303166373737623637636339653337
30383961643865386434346433626633653737306134653436666461623031623962366165663133
62343764623230363466636162633532303733363534373037323932373830663464643735343636
61343563333130663039643138363463336538313434616332356431376235383037623036373562
64666430613138343039383262393965666130383136336634343166323361646438326635303639
64373434303236633130626566373135613536633532386534343361333664356562366365666539
65653865393862376365633135333733643939663466376137396432363337326634323638643462
61376361643739316337373039383030376663343136393430333939346139326333613562333036
35383330616266323463653838323666356461633132626164633761656639396131336561666634
65303432666634643931616161366461343663336239376463316634393863383839313661303832
63386630646633646430316436333934306530626533363634636431303832386436633331613237
62653638393265613734633130313631306638646435663563393964623932613731303739616666
65393138396532656163643533313239323561643861313333623831336163626462643835666538
65393833663366356435346132323763383938376236393838316161353135323263373332386537
33336638313334303864633662336566353764373062636638626133336362656130343266313934
34303734363837306333643834373732386439613232326265343261353330386266396263643534
63316162356333313335343764633633643966303966636661346530393965643935396665346335
36366231653466323761643534373532666532666663333034353337383637393831623439623035
30613236643732653832333666366637343130333938386563616661613936333438643433626631
61383363353030386465613435643137393638626433323734346465373938643366343532366565
64383164356233343231373138353832393132636661613634646537363465353233333264303835
33386530393361396434333530343232393866663664356262663832623930613435363464313430
32313533376366623935323035356164353530396131376534646339633262626137376163316563
62653535626438383231363735633033653062343135333638326165393834353064656138323631
63303162346335333666393662626336303033353965643832373735623431633662336334363265
37656338613062343839326265393630343338383563623464396363663164313832663130336336
34633832336463663761353436643334323832396633323035623332623434326665343564633965
38643231386464386330643632363238643832303061633434356639373663313063396630363339
66386337376165303939333237396261393364646334313833383333323431653739326133363537
33613165376364396331333239623163306435373333373438303564643838633562353238333132
39646330666531633362356333633865623334366430653861653566636466323336326362623561
30666465646237646238346130353231333563336239346363653331616261653363346363393332
39616235386632313234323465616262643763646236333133643938663766636437313435653939
63663966643035616664316666613235613166666137396134636136346264313165323736656361
64626335313233343638623439343335613633373566363233306163383264376166316237363331
64353263613332663364663932653635383234303933356337363335356234326533376264323636
31366238313961363134373133626130666433653866383063613865363761306261393164343533
64303438393730653462383432343531303639313764653235396237633236363763346565313866
39363334643533333938383862626535613562666335633237333435643764623966636364643637
31333261663063333234643835323632376636333064333531396333356537306662316330366365
63363263623039643964303162343362353363386162626638373734666537643765356238613365
39633033396632653935653361353036393562323065303233366562613134353765393739663634
62366661366336666438633631653263363634306239343261383062333230363036366337386635
30663036333036376133653739633935636330353665353935333530383962643037393963313533
64336539333635323937656236323765306339383961353633383737353061346332363237366432
61396237396632323962633862343066656137636336333638646534386534396332663366643065
66336139373663613963663035306462333633613233616166303431386561373732383830626363
32383863666665666437386464353338646139623964663762336337306433623933313830333861
37356530643535666263323333303534336132366631343734383463643065393638666364626266
61323762343862393536663235396231303462663963323962373837633038643236316237373831
66343532383261326666653939303834613562343265333339333234373936626234636632363232
35356339343863376663666134363563336438616466323531306532306134316132626534333936
61373266623561363230313562306563383032366164663166303833623462336432623039366466
38336334663463646466386639336638613731393264663638393164333531303462346334643333
32323963356339373334646639313030383139346666343932666238613131353464356539366566
38373266323331333235373366623930623061333463616566346539353031343761393966373263
34366136393232646366616666333833663733316136396331383462666637313930646537623936
35383535353734316161373562356366326430663436373037376165613034373239343563626465
39393537613964646132626263393938643233363833613830386666373334663634366130343061
31636330653865313764326437313738383831363636666161366462663433616633666365623335
64613663333630653834363934663661356535303436663362643566306436663865613039623731
63313162653735313339393437353032333630333939353734383136663334316537316235373734
33323333323734663161653331303530323631656230663738616265373535323239363331656336
31643336616336386136323334636332396333343839306465333634333165653532323065636335
64666432326466626333356136653661313731646663393461613365366338343934623464313834
39373436396564353135623264613538333230653734326562343066343936353762636630626233
31623436656565306363353539306161353936373735613734663335313034666666333066636466
39353533626663376330303735333466386334313966616365626633366337336230623962383336
39303362393736336438396662656631396661363066666538613533346163353563316461633636
65303661373934616235653762383761616663346361363162663630663662323932646464306132
33656361303633383238386437643237366230383163346365336565353930303265356436666635
30626638653634343333636337376166373961633562623737323436363364306664663031613936
32613363353734633633323565313165616130643066333037356661663130663736373231393538
64356130633838343630623433323262363738613836633131383462653265613964333035303436
32616436333331653135396661373936636232336666623237643363346430366162326332343536
32656232666462663336306461646261393266613537313263306430656536346161653137643437
34383939623339653036613131326463333437623433303534656331396364333364323731323934
33396630653463653135663032386666363137643137323034306438643837616461306439663466
33373432623339346635646565616263346234663163326138636535386437333033393261613937
36353836303638303363306333343930626336383964356363366636383663366135333335346664
35343635346235626261376665366262646538363937353964373835646131653464363563666563
63303565303734376639326134376230303135393065623432336337656538306262333038373066
39393535626465656530333634316565336131323266633436393661646661353937396438363066
32313362333037343363303561313261636132313438643137343161316130383965376165303866
65626461656533626665356662643364343466623739376164626364373433313730346265363561
34373837356136396336616132323234313862663862626532356531396339613663373466643839
65653963373231353135316664363636353464333762646433666333613933616532356532323462
34656262313766326434663838633836636236326636663633363762393533333233313631366265
66643639643437633639396264303634623134393032383932316530323339323438643032303534
36666630636438393066613365643365393136663135633633393837306232653536366536626438
63656531643561333265373465656661306661626431336137376131623338383035636162346663
66333938396436316231613063386264623534623665636239613936343731363461326534306330
34616335363266393636383865326364653765653962623033336266336232626561633863633061
33336631643866663036366131633662323834326135646533306663356232356631653332366438
61343935363033396666356331323735316434626131343566333435623530373261343639346433
32313437646231396561313031623937623730386533376166386432303331313731323032373733
36663533316266303037303733323233323066643339616338656230396665363865323063643233
37623966616162333164326266373834393733343938356232643730643234353832306636386361
31383262303166326663656539313237623036633730323764363634313864343362643865343731
33326264383831623831376135376239333239616239623961366365383831333236623564663231
32323166336466313034323638353264656466313832336137633737316633343630643135616335
36306530346364353239346332643966323932343964663335343561373864646139353863376461
66626335393539323630646632636332333935353738336662303361646366626333626632366339
62353139323731623738303166313461303165616164336238303038306638356332313937623862
61373462393839343964383438653262653930666539393262376363646530623530396234363930
32646138366364623433643762656466353339663764323666303663326666326237663737376434
34663532623864373466666530393565646464613936613938346462333139656536393539643535
66353062326136323037343962343961623166343161366562383238626164386463353965643738
38336264303863616635313166383733616130323962303833643635623430393761386433356332
37643830336461383662306637363530366166353231323832653866383033303537663936316362
64663230383634363731383638343139643537666266366661326135353862663031643631623236
39343533623234323633643438316332336565386435616463663337383436353965646136353464
65363365663338663963663536326161393764396631343633613366616131626634623538373534
31613363633863653964633837316635313364326137363938313365323939323338396163373031
30396434383435366133363635383861393030323636366363613839343163663332386236633036
39666234613836623032616330376339636532326566636262353963663933353033393661636236
64663033636265663065646535373862393665306231656162646662313564653435633436383738
38323563383766356537656265346663396439383331626433393261393533363634353737383736
63333136373334616465616630626461373031663037363833626532646231663438656265663561
62303234633662613739353931626665356465373234383365346461633630313433326432636239
34653530393737646539356330363734636431626137316334393464656333306463653939616530
31633236326530333735646665643862356634646637373664646262613833323533346665366563
39316262396639613237346161313839323432626235303636343363646266663066623136376661
36356330333735396238363862353637303534643064633763306336326164326132333262626164
66313039326435303964633233313864336263633961343236316336333935353466626464656664
32343536663563623337383238626365666462653132373138643835656330653666333566643639
61636266643932383937346465356265376334366432386366336362333032343639623461346333
32313763643330643230313863623330393735626366623830613565333530376338336138386661
61663764336536663639343435343131356636313366613161643939663062663036363966383166
66363434626462653563356334393261656363383735323263653966363334366237373231663937
62356638313634343763326637636566613831353336373238343964306266383536636564373464
30656161653561626534343230386535663666393365303532383539346436373130663766663530
31303731323738316437356633343831633963353838653130366663626139653837666335643439
61396632363032663065376465353730626165653664366333633164356630663736396534623636
32653133303435366631323039613366393463636438323162363232316465663062316163633262
33363631643263383762333138643762376634343131336431383965303136643130346233343335
64376437306236386435376362383334323237663834383361396366336535666233346634336638
32343830633430633265313533613930373161393734623632636437633265393366623839333936
33366537393563313135656366653136313934613761383932393864316439376465633430633232
61663765306238396561663330313762616164333461323435376639613665636337616335303762
38653931326336356631333132363532303734646330316336626162323736656231663632623730
36636261373839363565346363356633396265646565343333343535393834613334313962323737
36666465653262643636633434663062306537623363336364323838656233646135643865326531
63633466373830336361383265616362646234363832316161326339396263373438656536323434
63363130313264653234626333383531343661343331356436653663343566373761636461653063
61663336643330303964613934643638646533313536663165663039376231373439316539356530
30366465336336653661303763623338376465366663313263353662376332663334633233353436
30336432393139666630386138316362386235323364666465643635393165623764616535346335
63613662323766363965326136613035353538336564303662376135393036636264366364393463
33363161653438613938306465646638393933366164336532623361353432653039343232303966
64636238616136613665656164636238316339343263636633306339386632353332626537343532
63323065313565333966303737373736336333343937336163333464343361613139373232646136
64313235663831313463313931623235636435336636653163316437313866643939393739656238
30333364613566626366633066613632383061623363343535396435656636623466663631656138
34343662346137613137633136326635343230373262303564653830306633366532623839333566
39636263616365623938343430383432323130316432646132616436393366363435386139633966
66333866633531303338656333613565313564393762353132323765356231356532653731323231
65353536323861386563386338346338346338646333373864366330623361383663343262643261
37633166343230363464663262376230643634636138633162396263386332326264323065383364
61633931633137613862333763316233393538343635353565623335386630666631636662616237
61336165376231306431393766363966306332303964396166396630373063666131626434363638
65323735616163393731383765393635393331663766636239633364643364333561303931616432
63663036343431373065626366333266373838636663393562393436313164633263393163373362
33303139333931326134323938633239366162336230636335383763386539646239356161313433
62386238323265333638643366366231633831623532613065343065373063336662613438366531
65396564343366346564366334663034306431363338353366366431356561393865396463306637
62386331343739626438656132343763656365353964373536323466636561396330626536336461
32353363663832336364383566306138643837373137643036633761653830653232366332623835
61613265653537643738393463323438613162353464333961653732623439333965376437633730
36636435343965646132376238303937323238336232306238366636373730616261333036326438
31646364343764653864386264346530313061316162353166653564383834353338383534656161
31636661336564313430623962316635663862333935323930626338323762626531643865396366
36663537383361306238383435653164346331633935333738376439353566303236636237373063
65353837656261613464323535393533353233323337646330316132363732303435363835643765
30636131646432306333653063326332376435343831636634633432653462306662393165346339
38633465626237346661373164626339396339636266306136373862353037316136396237636661
62333663353036383131623762616239363066316464663237633433343633323032336638663034
63363265613930353131613637323734623133623961366337613061323463386261613433653137
34313166636637313336366461383763356161363565383661633563313464306537393731633464
34353631396164396332313738363436623631636635643166303564363033366337626661313538
34633266616363303762373833316166356632356662353765313964383832313336323365663536
37663064646162646334376632656132663736643865353936613166623235623761613665393863
39633462623862323133363333643862373432363166316362656261346130626537313264383363
35343665336133373334336664356263336661316162343639666265396231376136313163636635
66336636643130383430656662653139633137373035323630333665626633306535623735656234
34633330353464306364616134373530643661363936646439313366383432376330656665383638
61613936323765356239316235323961613935313761346534363765396635636238363839626364
33373936353439663135363535633666373331343634333736376133383536656361376634393464
33343064383736613962313563353137306236343436653734656563653732636463393031616533
62633139666635373130336361663564383833333665303738666632663633393236383937343639
31323539363535613431656437636539323565366535613336396333343835623338663234353233
39313332313263353131656138663962373963323637346136373130336239616631393136303731
37303064653231636335306230383635663938623438306637366331326261373230313233666464
35386364633662623865643830383563303165653734303563633664643331656337616661323038
34656436646363346132373034363235346665643561616361393664393230613361313366656661
36363662313931393438373339383663626434343233643735316231623035373963326561313331
30653236363636663239393432663232626563626134336361663763653231336237366630326363
36633466626537646331343764343139663666623230636561656265396232613633386431633531
35363934396464386435343233356163376263386465616235333832316134303432383466633336
35363962323632633936313863346635613432666136626561353839623430333539363637663366
34343865653738313331363635386562383866646463643433353863303764326537326533633463
39663434326464313738393839366661323966373563333633636665356532313534643235663038
33636634396432363334626362653135623332613762333032363865653333643732616664303136
34336166323665373835633538373231376365366230623635353663333930303465343563343337
61663563366662623362303533376437393032306138363733653435373761376362333837363733
31366434613966383063376463646137653734306334393961636133393537356236383039663331
36663266393532663763303662353262373834356666303763383736666632313063666661373436
33623530376161313733386430656533626661653065363033663232396262346461396264623066
66326636366239303864646536303835383730376631623432363132376134663465656335353639
66613363353463616632373665393765373433613363306330333535393431626463626466666437
39303534366134373663373739373863646532643465623336643839623137363632323462646232
61636564643766323239376564623735306633343035613831656365653630396463366261313733
30656230373335653037353965663166303932363336613662633161633630643531353036633164
62653233326235396665316133336231386665316438613536343331353836326665386633366431
31326431613034353765306134366135316661306638366530323439616130643833316234383738
64336236626461373663383061376163316261626337626339656331393336326364343863646562
35363166643631396561393337623365653838643964323037346130346431323566663530653832
62623261666434336331306138346437363838323130343961363735393837343861333561363664
30353138393561383864396435376166303330626261653334396334356435356530326235633838
33666665643062353738393633313662383336326636633261333131636139376562623034363365
38343534333563306564616466313130356334663631313836353437353166316338363439386338
31373437353264666331623338323666323461376433653165373163656662623333393138666130
66626264323638613230373534356234666336323734383039393666353836306535653135663761
37333566313432623736636334646161373463656461333462363537383833363066366630636365
61633863326264666632663765333432333065393738333133326235356634646432313237366361
63663939663637663236653365616234343566656334313633313366303231363830313866316633
33343766393731663833636661363563663465613231393530643065383564663531376563633661
65656664346565363064346432323236393365356439666564663866316637356239353631396465
33313934356435363430333836333963386665653431333034653363383838343262373231323931
66346464356430663733363538383732356331343533616531383135333864636162323932346631
39656530636539623062373934333262323032363066626634323233323736363436333666303032
30633462646233616333393431393138393632653735303137356237326335663061363734363862
63633364633866643232643935373633663136323535333033666364613462643136323936343438
32386236326433653439313236373236366131333730643361363136656135306665323564646536
61396434393131353438326337316139396637663338663862333464373364646336353539663662
64383533396535653861396534633837323561333766346362656261643539353137316462646361
39363435363634653538643135656464373731316364313636633137613137366539643730383361
61376661666164616635376635343566653962393730383661653630363134333665623761613364
37366666356633383462613538633531343965333631613066326432623263316366633466313864
37613030383136623766373836353333363264313136386235383339306137323830663038303661
65333537393633366564616337366363306466356332333631363063303836396431356234323435
34353234386330336639363537623038643232353062376237316338393838373064636636363238
37356336376264383063613865613033386239356637316137633466313962383532373266346235
37356462636563343231326366373737616261393937353739396436303536646264303530363935
33306335643634366133393530666233393933623132636161386439343234636562623632376364
63613531343237346361383730383363353232303333323430613838346662333836373635376232
63373031313431373232346261376561663031356432303331303161396464626136613434653539
32636265353762636362666366393232643762343763303261343334363765353265633335393934
35363932626662303533343436303466623331346130626462633139303861343962356537323033
64626237396638363637373936353137663038393732633636316432316431613434323862646562
30363364373730343766326166346635373461353636393933393663336431363339383638386632
36323131656436643536363736306130316131346131343934613338366565356161366639313264
61633935666337373632363663336165333832373331613230333536636638646535343766613235
34313638396537366264653666306265323932363739376238346361346531663030393430316463
34323264373931316464363037323766333731363334393038613135393534383635616231313532
61613430386231343366343966613166616565326564633735353364346662653234646665663166
34346562626466626263653339376261356162643666666238356264613534363266366562363162
32363564373236656630353832353236343062623766663461656362376235346533376531343333
34373834383962326362643339666562663334323437633734333333353364633631356437313264
64626539323733613033333933363762346363376636356365393435653565353265383034366462
64633234393939383864363935313061633835643331323839353037663937376639646334633163
34323761373563383665336563663937343363396563623338383866623135643265643366353932
63323861636163376462333837663139353535613938646133613537646163626266363231396632
39316136373433316261656564306531353363666563343134373430616139656131343235393161
61353833366638336139373963623231613062326139336565313632363432373432313261626266
64626161326161373338383333323030643539313266313131383833363966386138326131303937
30303262346261333963336562333933363763643131333730363732333838376332393833663831
61646335343938376562666436633362633635353161326461396433343333393566356233346231
37303838303462336233336664633531353064386633323364626236613339363333616165653136
63393563393932313361343538623730353331303661306336383237336137356233303837306630
65386234383664303566343735383237363438363537373865643861373235366133613336386236
35306339623932386338616635656531306337633739316461303532326637353261633364306463
30663934636538393365376563333766663438356430393938383062346631666633356634616435
38636165663765346362633635643765363164306362323565316462323865373930643061353732
65386532636234656662643939346661353635376236303063333465383931366462633134353661
3966

View File

@@ -1,193 +0,0 @@
# Роль docker
## Описание
Универсальная роль Ansible для установки и настройки Docker и Docker Compose на различных Linux-дистрибутивах.
Роль автоматически определяет семейство ОС (Debian/Ubuntu или Red Hat/CentOS/AlmaLinux) и использует соответствующий метод установки.
**Поддерживаемые ОС:**
- Debian 9/10/11/12
- Ubuntu 20.04/22.04/24.04
- CentOS 7/8/9
- AlmaLinux 8/9
- Rocky Linux 8/9
- RHEL 8/9
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Переменные
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `docker_version` | `"latest"` | Версия Docker CE для установки |
| `docker_compose_version` | `"latest"` | Версия Docker Compose (для standalone версии) |
| `docker_use_official_repo` | `true` | Использовать официальный репозиторий Docker |
| `docker_use_compose_plugin` | `true` | Использовать docker-compose-plugin вместо standalone |
| `docker_users` | `[]` | Список пользователей для добавления в группу docker |
| `docker_service_enabled` | `true` | Автозапуск Docker при загрузке |
| `docker_service_state` | `started` | Состояние службы Docker |
| `docker_additional_packages` | `[]` | Дополнительные пакеты для установки |
| `docker_install_method` | `"official"` | Метод установки: 'official' или 'get.docker.com' |
## Примеры использования
### Базовое использование
```yaml
- hosts: all
roles:
- docker
```
### Установка с добавлением пользователей
```yaml
- hosts: all
roles:
- role: docker
vars:
docker_users:
- ansible
- deploy
```
### Установка через скрипт get.docker.com
```yaml
- hosts: all
roles:
- role: docker
vars:
docker_install_method: "get.docker.com"
docker_use_official_repo: false
```
### Установка standalone Docker Compose
```yaml
- hosts: all
roles:
- role: docker
vars:
docker_use_compose_plugin: false
docker_compose_version: "2.23.0"
```
### Установка с дополнительными пакетами
```yaml
- hosts: all
roles:
- role: docker
vars:
docker_additional_packages:
- docker-ce-rootless-extras
```
## Зависимости
Нет зависимостей от других ролей.
## Требования
- Ansible 2.9+
- Python 3.6+
- Права sudo на целевых хостах
- Доступ к интернету для загрузки пакетов
## Что делает роль
1. Определяет семейство ОС (Debian или Red Hat)
2. Устанавливает необходимые зависимости
3. Добавляет официальный репозиторий Docker
4. Устанавливает Docker CE и Docker Compose
5. Запускает и включает службу Docker
6. Создает группу docker
7. Добавляет пользователей в группу docker
8. Проверяет корректность установки
## Проверка установки
После выполнения роли проверьте установку:
```bash
# Проверка версии Docker
docker --version
# Проверка версии Docker Compose
docker compose version
# или
docker-compose --version
# Проверка статуса службы
systemctl status docker
# Проверка информации о Docker
docker info
```
## Пример playbook для тестирования
```yaml
---
- name: Установка Docker на все хосты
hosts: all
become: yes
roles:
- role: docker
vars:
docker_users:
- ansible
docker_use_compose_plugin: true
post_tasks:
- name: Запуск тестового контейнера
docker_container:
name: hello-world
image: hello-world:latest
state: started
- name: Проверка запущенных контейнеров
command: docker ps -a
register: docker_ps
changed_when: false
- name: Вывод списка контейнеров
debug:
var: docker_ps.stdout_lines
```
## Troubleshooting
### Docker не запускается
Проверьте журналы systemd:
```bash
sudo journalctl -u docker.service
```
### Проблемы с репозиторием
Для Ubuntu/Debian, если репозиторий не добавляется:
```bash
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release
```
### Проблемы с правами
Если пользователь не может запускать docker без sudo:
```bash
sudo usermod -aG docker $USER
# Выйдите и войдите снова
```
## Лицензия
MIT
## Автор
Сергей Антропов - https://devops.org.ru

View File

@@ -1,33 +0,0 @@
---
# Переменные по умолчанию для роли docker
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Версия Docker CE
docker_version: "latest"
# Версия Docker Compose (если не используется docker-compose-plugin)
docker_compose_version: "latest"
# Использовать ли официальный репозиторий Docker (рекомендуется)
docker_use_official_repo: true
# Использовать ли docker-compose-plugin (вместо standalone docker-compose)
docker_use_compose_plugin: true
# Пользователь для добавления в группу docker
docker_users: []
# - ansible
# - deploy
# Автозапуск Docker при загрузке системы
docker_service_enabled: true
# Автозапуск Docker при загрузке системы
docker_service_state: started
# Дополнительные пакеты для установки
docker_additional_packages: []
# Метод установки: 'official' (официальный репозиторий) или 'get.docker.com' (скрипт)
docker_install_method: "get.docker.com"

View File

@@ -1,22 +0,0 @@
---
# Обработчики для роли docker
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Restart docker
become: true
systemd:
name: docker
state: restarted
- name: Reload docker
become: true
systemd:
name: docker
state: reloaded
- name: Restart docker socket
become: true
systemd:
name: docker.socket
state: restarted

View File

@@ -1,35 +0,0 @@
---
galaxy_info:
author: Сергей Антропов
description: Универсальная роль Ansible для установки и настройки Docker и Docker Compose на различных Linux-дистрибутивах
company: https://devops.org.ru
license: MIT
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions:
- focal
- jammy
- noble
- name: Debian
versions:
- stretch
- buster
- bullseye
- bookworm
- name: EL
versions:
- "7"
- "8"
- "9"
- name: Rocky
versions:
- "8.0"
- "9.0"
galaxy_tags:
- docker
- dockercompose
- container
- virtualization
- linux
dependencies: []

View File

@@ -1,279 +0,0 @@
---
# Задачи для роли docker
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: "Reset ANSI color codes"
debug:
msg: "\033[0m"
changed_when: false
tags: [docker, color-reset]
# Определяем семейство ОС для выбора правильного метода установки
- name: Определение семейства ОС
set_fact:
docker_os_family: "{{ ansible_os_family }}"
docker_pkg_manager: "{{ 'yum' if ansible_pkg_mgr == 'yum' else ('dnf' if ansible_pkg_mgr == 'dnf' else 'apt') }}"
# Устанавливаем предварительные зависимости
- name: Установка зависимостей для Debian/Ubuntu
become: true
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- software-properties-common
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Установка зависимостей для Red Hat
become: true
package:
name:
- yum-utils
- device-mapper-persistent-data
- lvm2
state: present
when: ansible_os_family == "RedHat"
failed_when: false
# Добавляем официальный репозиторий Docker для Debian/Ubuntu
- name: Добавление GPG ключа Docker для Debian/Ubuntu
become: true
shell: |
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/{{ (ansible_distribution | lower) }}/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
args:
creates: /etc/apt/keyrings/docker.asc
when:
- ansible_os_family == "Debian"
- docker_use_official_repo
- name: Добавление репозитория Docker для Debian/Ubuntu
become: true
apt_repository:
repo: "deb [arch={{ ansible_architecture }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/{{ (ansible_distribution | lower) }} {{ ansible_distribution_release }} stable"
state: present
filename: docker
update_cache: yes
when:
- ansible_os_family == "Debian"
- docker_use_official_repo
# Добавляем официальный репозиторий Docker для Red Hat
- name: Добавление репозитория Docker для Red Hat через dnf
become: true
shell: |
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
args:
creates: /etc/yum.repos.d/docker-ce.repo
when:
- ansible_os_family == "RedHat"
- ansible_pkg_mgr == "dnf"
- docker_use_official_repo
failed_when: false
- name: Добавление репозитория Docker для Red Hat через yum-config-manager
become: true
shell: |
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
args:
creates: /etc/yum.repos.d/docker-ce.repo
when:
- ansible_os_family == "RedHat"
- ansible_pkg_mgr == "yum"
- docker_use_official_repo
failed_when: false
# Устанавливаем Docker через официальный репозиторий
- name: Установка Docker CE для Debian/Ubuntu (официальный репозиторий)
become: true
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
state: "{{ 'present' if docker_version == 'latest' else docker_version }}"
update_cache: yes
when:
- ansible_os_family == "Debian"
- docker_use_official_repo
- docker_install_method == "official"
- name: Установка Docker CE для Red Hat (официальный репозиторий)
become: true
package:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
state: "{{ 'present' if docker_version == 'latest' else docker_version }}"
when:
- ansible_os_family == "RedHat"
- docker_use_official_repo
- docker_install_method == "official"
# Устанавливаем Docker через скрипт get.docker.com
- name: Проверка наличия Docker
stat:
path: /usr/bin/docker
register: docker_binary
- name: Скачивание скрипта установки Docker
become: true
get_url:
url: https://get.docker.com
dest: /tmp/get-docker.sh
mode: '0755'
when:
- docker_install_method == "get.docker.com" or not docker_use_official_repo
- not docker_binary.stat.exists
- name: Установка Docker через скрипт get.docker.com
become: true
command: /tmp/get-docker.sh
args:
creates: /usr/bin/docker
when:
- docker_install_method == "get.docker.com" or not docker_use_official_repo
- not docker_binary.stat.exists
- name: Проверка установки Docker
become: true
command: docker --version
register: docker_version_check
changed_when: false
failed_when: false
- name: Вывод версии Docker
debug:
msg: "{{ docker_version_check.stdout if docker_version_check.rc == 0 else 'Docker не установлен' }}"
# Устанавливаем docker-compose-plugin для Debian/Ubuntu
- name: Установка docker-compose-plugin для Debian/Ubuntu
become: true
apt:
name:
- docker-compose-plugin
state: present
when:
- ansible_os_family == "Debian"
- docker_use_compose_plugin
- docker_use_official_repo
# Устанавливаем docker-compose-plugin для Red Hat
- name: Установка docker-compose-plugin для Red Hat
become: true
package:
name:
- docker-compose-plugin
state: present
when:
- ansible_os_family == "RedHat"
- docker_use_compose_plugin
- docker_use_official_repo
# Устанавливаем standalone docker-compose
- name: Скачивание docker-compose
become: true
get_url:
url: "https://github.com/docker/compose/releases/{{ 'latest/download' if docker_compose_version == 'latest' else 'download/v' + docker_compose_version }}/docker-compose-{{ ansible_system }}-{{ ansible_machine }}"
dest: /usr/local/bin/docker-compose
mode: '0755'
when:
- not docker_use_compose_plugin or docker_compose_version != 'latest'
- name: Проверка установки Docker Compose
become: true
shell: |
set -o pipefail
if docker compose version >/dev/null 2>&1; then
docker compose version --short 2>/dev/null || docker compose version
elif docker-compose version >/dev/null 2>&1; then
docker-compose --version
else
echo "Docker Compose не установлен"
fi
register: docker_compose_version_check
changed_when: false
failed_when: false
- name: Вывод версии Docker Compose
debug:
msg: "{{ docker_compose_version_check.stdout if docker_compose_version_check.rc == 0 else 'Docker Compose не установлен' }}"
# Определяем универсальную команду для запуска Compose (docker compose или docker-compose)
- name: Определение команды Docker Compose
become: true
shell: |
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
echo "docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
echo "docker-compose"
else
echo ""
fi
register: compose_cmd_detect
changed_when: false
- name: Установка факта compose_cmd
set_fact:
compose_cmd: "{{ compose_cmd_detect.stdout | default('') }}"
- name: Предупреждение при отсутствии Docker Compose
debug:
msg: "⚠️ Docker Compose не найден; установите docker-compose-plugin или docker-compose"
when: compose_cmd == ""
# Запускаем и включаем службу Docker
- name: Запуск службы Docker
become: true
systemd:
name: docker
state: started
enabled: yes
# Создаем группу docker
- name: Создание группы docker
become: true
group:
name: docker
state: present
# Добавляем пользователей в группу docker
- name: Добавление пользователей в группу docker
become: true
user:
name: "{{ item }}"
groups: docker
append: yes
loop: "{{ docker_users }}"
when: docker_users | length > 0
# Устанавливаем дополнительные пакеты
- name: Установка дополнительных пакетов
become: true
package:
name: "{{ docker_additional_packages }}"
state: present
when: docker_additional_packages | length > 0
# Проверка статуса Docker
- name: Проверка статуса Docker
become: true
command: docker info
register: docker_info
changed_when: false
failed_when: false
- name: Вывод информации о Docker
debug:
msg: "Docker успешно установлен и запущен"
when: docker_info.rc == 0

View File

@@ -1,6 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
38383165633431343739343231366365353762393539366439316236623864396532336635373136
6564333336613838323665616333633232356333623635330a383066316434646339623636343134
62336265323664386533333965336236623138616264626265383264633962653065646163333363
3333613736636562610a663364363939633639383264643738346363343232333666393434626562
3464

View File

@@ -1,246 +0,0 @@
# Роль Python 3.12
Универсальная Ansible роль для установки Python 3.12 на различных дистрибутивах Linux.
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Описание
Эта роль обеспечивает универсальную установку Python 3.12 на различных дистрибутивах Linux, включая:
- **Ubuntu** (20.04, 22.04, 24.04)
- **Debian** (10, 11, 12)
- **CentOS** (7, 8, 9)
- **RHEL** (7, 8, 9)
- **Rocky Linux** (8, 9)
- **AlmaLinux** (8, 9)
- **Fedora** (35+)
- **openSUSE** (15.3+)
Роль автоматически определяет дистрибутив и использует соответствующий метод установки:
- Установка из пакетов (если доступно)
- Компиляция из исходного кода (если пакеты недоступны)
- Автоматическое обновление pip до последней версии
- Создание символических ссылок для удобства использования
- Поддержка виртуальных окружений
## Требования
- Ansible >= 2.9
- Python 2.7 или 3.5+ на управляющей машине
- Привилегии sudo на целевых хостах
## Переменные
### Основные переменные
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `python_version` | `"3.12"` | Версия Python для установки |
| `python_packages` | `["pip", "setuptools", "wheel", "virtualenv"]` | Дополнительные пакеты Python |
| `python_install_prefix` | `"/usr/local"` | Префикс для установки Python |
| `python_create_symlinks` | `true` | Создание символических ссылок |
| `python_update_pip` | `true` | Автоматическое обновление pip до последней версии |
| `python_pip_packages` | `[]` | Дополнительные pip пакеты |
| `python_create_venv` | `false` | Создание виртуального окружения |
| `python_venv_path` | `"/opt/python-venv"` | Путь для виртуального окружения |
| `python_setup_alternatives` | `true` | Настройка альтернатив (RHEL) |
| `python_remove_old_versions` | `false` | Удаление старых версий Python |
| `python_log_level` | `"INFO"` | Уровень логирования |
### Переменные для разных ОС
Роль автоматически определяет переменные на основе `ansible_os_family` и `ansible_distribution`:
- `python_current_packages` - пакеты Python для текущей ОС
- `python_current_build_deps` - системные зависимости для компиляции
- `python_current_package_manager` - менеджер пакетов
- `python_current_executable` - путь к исполняемому файлу Python
- `python_current_pip` - путь к pip
## Примеры использования
### Базовое использование
```yaml
- hosts: all
roles:
- python
```
### С дополнительными пакетами
```yaml
- hosts: all
vars:
python_pip_packages:
- requests
- flask
- django
python_create_venv: true
python_venv_path: "/opt/myapp-venv"
roles:
- python
```
### С компиляцией из исходного кода
```yaml
- hosts: all
vars:
python_install_prefix: "/opt/python3.11"
python_create_symlinks: true
roles:
- python
```
### Для конкретной версии Python
```yaml
- hosts: all
vars:
python_version: "3.12.0"
roles:
- python
```
## Поддерживаемые дистрибутивы
### Debian/Ubuntu
Роль использует репозиторий `deadsnakes` для установки Python 3.12:
```yaml
- hosts: ubuntu_servers
roles:
- python
```
### RHEL/CentOS/Rocky/AlmaLinux
Роль использует EPEL репозиторий для установки Python 3.12:
```yaml
- hosts: rhel_servers
roles:
- python
```
### Fedora
```yaml
- hosts: fedora_servers
roles:
- python
```
### openSUSE
```yaml
- hosts: suse_servers
roles:
- python
```
## Альтернативные репозитории
Для старых дистрибутивов, которые не поддерживают Python 3.12 из стандартных репозиториев, роль автоматически использует альтернативные источники:
### RHEL/CentOS 7
- **IUS Repository**: Предоставляет современные версии Python
- **SCL (Software Collections)**: Для очень старых систем
- **EPEL**: Дополнительные пакеты
### Debian/Ubuntu старых версий
- **deadsnakes PPA**: Предоставляет Python 3.12 для старых версий
- **Альтернативные пакеты**: Python 3.11 если 3.12 недоступен
### Автоматическое переключение
Роль автоматически пытается установить Python 3.12, а если это невозможно, переключается на доступную версию (3.11) или использует альтернативные репозитории.
## Структура роли
```
roles/python/
├── defaults/
│ └── main.yml # Переменные по умолчанию
├── handlers/
│ └── main.yml # Обработчики событий
├── meta/
│ └── main.yml # Метаданные роли
├── tasks/
│ └── main.yml # Основные задачи
├── vars/
│ └── main.yml # Переменные для разных ОС
└── README.md # Документация
```
## Логирование
Роль поддерживает подробное логирование. Установите `python_log_level: "DEBUG"` для получения детальной информации о процессе установки.
## Обработка ошибок
Роль включает проверки на каждом этапе:
1. Проверка наличия Python перед установкой
2. Проверка успешности установки
3. Проверка работоспособности pip
4. Очистка временных файлов
## Производительность
- Использует параллельную компиляцию (количество ядер CPU)
- Оптимизированная конфигурация Python с LTO
- Кэширование пакетов
- Очистка временных файлов
## Безопасность
- Проверка целостности загружаемых файлов
- Использование официальных репозиториев
- Минимальные привилегии для установки
## Устранение неполадок
### Python не найден после установки
```bash
# Проверьте символические ссылки
ls -la /usr/bin/python3*
# Обновите библиотеки
sudo ldconfig
```
### Ошибки компиляции
```bash
# Установите системные зависимости
sudo apt install build-essential # Ubuntu/Debian
sudo dnf groupinstall "Development Tools" # RHEL/CentOS
```
### Проблемы с pip
```bash
# Обновите pip
python3 -m pip install --upgrade pip
# Проверьте версию
pip3 --version
```
## Лицензия
MIT
## Автор
**Сергей Антропов**
Сайт: https://devops.org.ru
## Поддержка
Для получения поддержки или сообщения об ошибках, пожалуйста, создайте issue в репозитории проекта.

View File

@@ -1,60 +0,0 @@
---
# Переменные по умолчанию для роли python
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Версия Python для установки
python_version: "3.12"
# Установка дополнительных пакетов Python
python_packages:
- pip
- setuptools
- wheel
- virtualenv
# Установка системных зависимостей для компиляции Python
python_build_dependencies:
- gcc
- gcc-c++
- make
- zlib-devel
- openssl-devel
- libffi-devel
- sqlite-devel
- readline-devel
- tk-devel
- gdbm-devel
- db4-devel
- libpcap-devel
- xz-devel
- expat-devel
- bzip2-devel
- ncurses-devel
- libuuid-devel
- libnsl2-devel
# Путь для установки Python
python_install_prefix: "/usr/local"
# Создание символических ссылок
python_create_symlinks: true
# Обновление pip после установки
python_update_pip: true
# Установка дополнительных pip пакетов
python_pip_packages: []
# Создание виртуального окружения
python_create_venv: false
python_venv_path: "/opt/python-venv"
# Настройка альтернатив (для систем с alternatives)
python_setup_alternatives: true
# Удаление старых версий Python (осторожно!)
python_remove_old_versions: false
# Логирование
python_log_level: "INFO"

View File

@@ -1,44 +0,0 @@
---
# Обработчики для роли python
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: "Перезапуск systemd для обновления библиотек"
systemd:
daemon_reload: yes
when: ansible_service_mgr == "systemd"
- name: "Обновление кэша библиотек"
command: "ldconfig"
changed_when: false
when: ansible_os_family == "RedHat"
- name: "Обновление кэша пакетов"
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: "Очистка кэша пакетов"
command: "{{ item }}"
loop:
- "dnf clean all"
- "yum clean all"
changed_when: false
when: ansible_os_family == "RedHat"
ignore_errors: true
- name: "Уведомление о завершении установки Python"
debug:
msg: |
✅ PYTHON {{ python_version | upper }} УСТАНОВЛЕН И НАСТРОЕН!
🎯 Основные команды:
• python --version # Проверить версию Python
• python3 --version # Проверить версию Python (с версией)
• pip --version # Проверить версию pip
• pip3 --version # Проверить версию pip (с версией)
• python -m venv env # Создать виртуальное окружение
• pip install pkg # Установить пакет
🚀 Готово к работе!

View File

@@ -1,54 +0,0 @@
---
# Метаданные роли python
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
galaxy_info:
author: "Сергей Антропов"
description: "Универсальная роль для установки Python 3.12 на различных дистрибутивах Linux"
company: "DevOps Lab"
license: "MIT"
min_ansible_version: "2.9"
platforms:
- name: "Ubuntu"
versions:
- "focal"
- "jammy"
- "noble"
- name: "Debian"
versions:
- "buster"
- "bullseye"
- "bookworm"
- name: "EL"
versions:
- "7"
- "8"
- "9"
- name: "Rocky"
versions:
- "8.8"
- "9.0"
- name: "Fedora"
versions:
- "35"
- "36"
- "37"
- "38"
- "39"
- "40"
- name: "opensuse"
versions:
- "15.3"
- "15.4"
- "15.5"
galaxy_tags:
- "python"
- "programming"
- "development"
- "runtime"
- "language"
- "universal"
- "crossplatform"
dependencies: []

View File

@@ -1,595 +0,0 @@
---
# Основные задачи для установки Python 3.12
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: "Reset ANSI color codes"
debug:
msg: "\033[0m"
changed_when: false
tags: [python, color-reset]
- name: "Отладочная информация о системе"
debug:
msg:
- "ansible_distribution: '{{ ansible_distribution }}'"
- "ansible_os_family: '{{ ansible_os_family }}'"
- "ansible_distribution_version: '{{ ansible_distribution_version }}'"
when: (python_log_level | default("INFO")) in ["DEBUG", "INFO"]
- name: "Определение переменных для текущей ОС"
set_fact:
python_distribution: "{{ ansible_distribution | lower }}"
python_os_family: "{{ ansible_os_family | lower }}"
python_current_package_manager: "{{ python_package_managers[ansible_distribution | lower] | default('unknown') }}"
python_current_packages: "{{ python_packages_by_os[ansible_distribution | lower] | default([]) }}"
python_current_build_deps: "{{ python_build_deps_by_os[ansible_distribution | lower] | default([]) }}"
python_current_executable: "{{ python_executable_paths[ansible_distribution | lower] | default('/usr/bin/python3.12') }}"
python_current_pip: "{{ python_pip_paths[ansible_distribution | lower] | default('/usr/bin/pip3.12') }}"
- name: "Проверка определенных переменных"
debug:
msg:
- "python_distribution: '{{ python_distribution }}'"
- "python_os_family: '{{ python_os_family }}'"
- "python_current_package_manager: '{{ python_current_package_manager }}'"
- "python_current_packages: {{ python_current_packages }}"
- "python_current_build_deps: {{ python_current_build_deps }}"
- "python_current_executable: '{{ python_current_executable }}'"
- "python_current_pip: '{{ python_current_pip }}'"
when: (python_log_level | default("INFO")) in ["DEBUG", "INFO"]
# =============================================================================
# ЭТАП 1: ДОБАВЛЕНИЕ РЕПОЗИТОРИЕВ
# =============================================================================
- name: "Добавление репозиториев для Ubuntu/Debian"
apt_repository:
repo: "{{ item.url }}"
state: "{{ item.state }}"
update_cache: yes
loop: "{{ python_repositories[python_distribution] | default([]) }}"
when:
- python_distribution in ['ubuntu', 'debian']
- python_repositories[python_distribution] is defined
- item.when is not defined or (item.when | default(true))
ignore_errors: true
- name: "Добавление репозиториев для RHEL-семейства"
package:
name: "{{ item.name }}"
state: "{{ item.state }}"
loop: "{{ python_repositories[python_distribution] | default([]) }}"
when:
- python_distribution in ['redhat', 'centos', 'rhel', 'rocky', 'alma', 'fedora']
- python_repositories[python_distribution] is defined
- item.name != 'scl'
ignore_errors: true
- name: "Добавление SCL репозиториев для старых RHEL/CentOS"
yum_repository:
name: "{{ item.name }}"
description: "{{ item.name }} repository"
baseurl: "{{ item.url }}"
gpgcheck: no
enabled: yes
loop: "{{ python_repositories[python_distribution] | default([]) }}"
when:
- python_distribution in ['redhat', 'centos', 'rhel']
- python_repositories[python_distribution] is defined
- item.name == 'scl'
ignore_errors: true
# =============================================================================
# ЭТАП 2: ОБНОВЛЕНИЕ ПАКЕТОВ
# =============================================================================
- name: "Обновление списка пакетов"
package:
name: "*"
state: present
when:
- python_current_package_manager in ['apt', 'dnf', 'zypper']
- python_current_package_manager != 'unknown'
- name: "Обновление списка пакетов (ClearLinux)"
command: "swupd update"
changed_when: false
when:
- python_current_package_manager == 'swupd'
- python_current_package_manager != 'unknown'
ignore_errors: true
- name: "Обновление списка пакетов после добавления репозиториев"
package:
name: "*"
state: present
when:
- python_repositories[python_distribution] is defined
- python_current_package_manager in ['apt', 'dnf']
# =============================================================================
# ЭТАП 3: УСТАНОВКА СИСТЕМНЫХ ЗАВИСИМОСТЕЙ
# =============================================================================
- name: "Установка системных зависимостей для компиляции"
package:
name: "{{ python_current_build_deps }}"
state: present
when:
- python_current_build_deps | length > 0
- python_current_package_manager != 'unknown'
- python_current_package_manager != 'swupd'
ignore_errors: true
- name: "Установка системных зависимостей для компиляции (ClearLinux)"
command: "swupd bundle-add {{ python_current_build_deps | join(' ') }}"
when:
- python_current_build_deps | length > 0
- python_current_package_manager == 'swupd'
changed_when: false
ignore_errors: true
# =============================================================================
# ЭТАП 4: ПРОВЕРКА И УСТАНОВКА PYTHON СТАНДАРТНЫМ СПОСОБОМ
# =============================================================================
- name: "Проверка наличия Python {{ python_version }}"
command: "{{ python_current_executable }} --version"
register: python_version_check
failed_when: false
changed_when: false
when: python_current_executable is defined and python_current_executable != ""
- name: "Установка Python из пакетов (основной способ)"
package:
name: "{{ python_current_packages }}"
state: present
when:
- python_current_packages | length > 0
- python_current_package_manager != 'unknown'
- python_current_package_manager != 'swupd'
- python_version_check.rc != 0
register: python_package_install
ignore_errors: true
- name: "Установка Python из пакетов (ClearLinux)"
command: "swupd bundle-add {{ python_current_packages | join(' ') }}"
when:
- python_current_packages | length > 0
- python_current_package_manager == 'swupd'
- python_version_check.rc != 0
register: python_package_install
changed_when: false
ignore_errors: true
# =============================================================================
# ЭТАП 5: FALLBACK НА АЛЬТЕРНАТИВНЫЕ СПОСОБЫ
# =============================================================================
- name: "Попытка установки альтернативных пакетов Python"
package:
name: "{{ python_current_packages | select('match', '.*python3\\.1[12].*') | list }}"
state: present
when:
- python_package_install is defined
- python_package_install.failed | default(false)
- python_current_package_manager != 'unknown'
- python_version_check.rc != 0
register: python_alt_install
ignore_errors: true
- name: "Попытка установки SCL пакетов Python"
package:
name: "{{ python_current_packages | select('match', '.*rh-python.*') | list }}"
state: present
when:
- python_alt_install is defined
- python_alt_install.failed | default(false)
- python_current_package_manager != 'unknown'
- python_version_check.rc != 0
- python_distribution in ['redhat', 'centos', 'rhel']
register: python_scl_install
ignore_errors: true
# =============================================================================
# ЭТАП 6: КОМПИЛЯЦИЯ ИЗ ИСХОДНИКОВ (ПОСЛЕДНИЙ СПОСОБ)
# =============================================================================
- name: "Скачивание исходного кода Python {{ python_version }}"
get_url:
url: "https://www.python.org/ftp/python/{{ python_version }}/Python-{{ python_version }}.tar.xz"
dest: "/tmp/Python-{{ python_version }}.tar.xz"
mode: '0644'
register: download_result
failed_when: false
changed_when: false
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- python_package_install is defined
- python_package_install.failed | default(false)
- python_alt_install is defined
- python_alt_install.failed | default(false)
ignore_errors: true
- name: "Распаковка исходного кода Python"
unarchive:
src: "/tmp/Python-{{ python_version }}.tar.xz"
dest: "/tmp/"
remote_src: yes
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- download_result is defined
- download_result.rc is defined and download_result.rc == 0
- name: "Конфигурация Python для компиляции"
command: >
./configure
--prefix={{ python_install_prefix | default('/usr/local') }}
--enable-optimizations
--enable-shared
--with-lto
--enable-ipv6
--with-system-ffi
--with-computed-gotos
--enable-loadable-sqlite-extensions
args:
chdir: "/tmp/Python-{{ python_version }}"
changed_when: false
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- download_result is defined
- download_result.rc is defined and download_result.rc == 0
- name: "Компиляция Python"
make:
chdir: "/tmp/Python-{{ python_version }}"
jobs: "{{ ansible_processor_cores | default(1) }}"
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- download_result is defined
- download_result.rc is defined and download_result.rc == 0
- name: "Установка скомпилированного Python"
make:
chdir: "/tmp/Python-{{ python_version }}"
target: install
become: true
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- download_result is defined
- download_result.rc is defined and download_result.rc == 0
- name: "Обновление библиотек для скомпилированного Python"
command: "ldconfig"
become: true
changed_when: false
when:
- python_current_packages | length == 0
- python_version_check.rc != 0
- download_result is defined
- download_result.rc is defined and download_result.rc == 0
# =============================================================================
# ЭТАП 7: СОЗДАНИЕ СИМВОЛИЧЕСКИХ ССЫЛОК
# =============================================================================
- name: "Создание символических ссылок для Python"
file:
src: "{{ python_current_executable }}"
dest: "/usr/bin/python3"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- name: "Создание символических ссылок для SCL Python"
file:
src: "/opt/rh/rh-python312/root/usr/bin/python3"
dest: "/usr/bin/python3"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_distribution in ['redhat', 'centos', 'rhel']
- python_scl_install is defined
- python_scl_install.changed | default(false)
- name: "Создание символических ссылок для pip"
file:
src: "{{ python_current_pip }}"
dest: "/usr/bin/pip3"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_current_pip is defined
- python_current_pip != ""
- name: "Создание символических ссылок для SCL pip"
file:
src: "/opt/rh/rh-python312/root/usr/bin/pip3"
dest: "/usr/bin/pip3"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_distribution in ['redhat', 'centos', 'rhel']
- python_scl_install is defined
- python_scl_install.changed | default(false)
- name: "Создание символических ссылок для Python (без версии)"
file:
src: "{{ python_current_executable }}"
dest: "/usr/bin/python"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- name: "Создание символических ссылок для SCL Python (без версии)"
file:
src: "/opt/rh/rh-python312/root/usr/bin/python"
dest: "/usr/bin/python"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_distribution in ['redhat', 'centos', 'rhel']
- python_scl_install is defined
- python_scl_install.changed | default(false)
- name: "Создание символических ссылок для pip (без версии)"
file:
src: "{{ python_current_pip }}"
dest: "/usr/bin/pip"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_current_pip is defined
- python_current_pip != ""
- name: "Создание символических ссылок для SCL pip (без версии)"
file:
src: "/opt/rh/rh-python312/root/usr/bin/pip"
dest: "/usr/bin/pip"
state: link
force: yes
when:
- (python_create_symlinks | default(true)) | bool
- python_version_check.rc != 0
- python_distribution in ['redhat', 'centos', 'rhel']
- python_scl_install is defined
- python_scl_install.changed | default(false)
# =============================================================================
# ЭТАП 8: НАСТРОЙКА АЛЬТЕРНАТИВ И PIP
# =============================================================================
- name: "Настройка альтернатив для Python"
alternatives:
name: python3
path: "{{ python_current_executable }}"
when:
- python_current_executable is defined
- python_current_executable != ""
- ansible_os_family == "RedHat"
- name: "Установка pip через get-pip.py если не найден"
get_url:
url: "https://bootstrap.pypa.io/get-pip.py"
dest: "/tmp/get-pip.py"
mode: '0755'
when:
- python_version_check.rc == 0
- python_current_pip is not defined or python_current_pip == ""
- name: "Запуск get-pip.py для установки pip"
command: "{{ python_current_executable }} /tmp/get-pip.py"
changed_when: false
when:
- python_version_check.rc == 0
- python_current_pip is not defined or python_current_pip == ""
# =============================================================================
# ЭТАП 9: ОБНОВЛЕНИЕ PIP
# =============================================================================
- name: "Получение последней версии pip"
uri:
url: "https://pypi.org/pypi/pip/json"
method: GET
register: pip_version_info
when: python_version_check.rc == 0
ignore_errors: true
- name: "Извлечение версии pip"
set_fact:
pip_latest_version: "{{ pip_version_info.json.info.version }}"
when:
- pip_version_info is defined
- (pip_version_info.status | default(200)) == 200
- pip_version_info.json is defined
- pip_version_info.json.info is defined
- name: "Проверка текущей версии pip"
command: "{{ python_current_pip }} --version"
register: pip_current_version
changed_when: false
when:
- python_version_check.rc == 0
- python_current_pip is defined
- python_current_pip != ""
ignore_errors: true
- name: "Обновление pip до последней версии"
command: "{{ python_current_pip }} install --upgrade pip"
changed_when: false
when:
- python_version_check.rc == 0
- pip_current_version.rc == 0
- pip_latest_version is defined
- pip_current_version.stdout is defined
- pip_latest_version not in pip_current_version.stdout
- python_current_package_manager != 'swupd'
ignore_errors: true
- name: "Обновление pip до последней версии (ClearLinux)"
command: "{{ python_current_pip }} install --upgrade pip --break-system-packages"
changed_when: false
when:
- python_version_check.rc == 0
- pip_current_version.rc == 0
- pip_latest_version is defined
- pip_current_version.stdout is defined
- pip_latest_version not in pip_current_version.stdout
- python_current_package_manager == 'swupd'
ignore_errors: true
# =============================================================================
# ЭТАП 10: СОЗДАНИЕ ВИРТУАЛЬНОГО ОКРУЖЕНИЯ
# =============================================================================
- name: "Создание виртуального окружения"
command: "{{ python_current_executable }} -m venv {{ python_venv_path | default('/opt/python-venv') }}"
changed_when: false
when:
- (python_create_venv | default(false)) | bool
- python_version_check.rc == 0
ignore_errors: true
# =============================================================================
# ЭТАП 11: ФИНАЛЬНЫЙ ОТЧЕТ
# =============================================================================
- name: "Сбор информации о системе"
setup:
gather_subset:
- "!all"
- "distribution"
- "os_family"
- "architecture"
- "kernel"
- "python"
register: system_facts
- name: "Проверка установленного Python"
command: "{{ python_current_executable }} --version"
register: final_python_version
changed_when: false
when: python_version_check.rc == 0
ignore_errors: true
- name: "Проверка установленного pip"
command: "{{ python_current_pip }} --version"
register: final_pip_version
changed_when: false
when:
- python_version_check.rc == 0
- python_current_pip is defined
- python_current_pip != ""
ignore_errors: true
- name: "Проверка символических ссылок"
stat:
path: "{{ item }}"
register: symlink_check
loop:
- "/usr/bin/python"
- "/usr/bin/python3"
- "/usr/bin/pip"
- "/usr/bin/pip3"
- name: "Проверка виртуального окружения"
stat:
path: "{{ python_venv_path | default('/opt/python-venv') }}"
register: venv_check
when: python_create_venv | bool
- name: "Сбор информации об установленных пакетах Python"
package_facts:
manager: "{{ python_current_package_manager }}"
when: python_current_package_manager != 'unknown'
ignore_errors: true
- name: "Финальный отчет об установке"
debug:
msg: |
================================================================================
🐍 ОТЧЕТ ОБ УСТАНОВКЕ PYTHON {{ python_version | upper }}
================================================================================
📊 ИНФОРМАЦИЯ О СИСТЕМЕ:
• Дистрибутив: {{ ansible_distribution }} {{ ansible_distribution_version }}
• Семейство ОС: {{ ansible_os_family }}
• Архитектура: {{ ansible_architecture }}
• Ядро: {{ ansible_kernel }}
🐍 PYTHON:
• Версия: {{ final_python_version.stdout | default('НЕ УСТАНОВЛЕН') }}
• Исполняемый файл: {{ python_current_executable }}
• Путь к pip: {{ python_current_pip | default('НЕ НАЙДЕН') }}
📦 PIP:
• Версия: {{ final_pip_version.stdout | default('НЕ УСТАНОВЛЕН') }}
🔗 СИМВОЛИЧЕСКИЕ ССЫЛКИ:
• /usr/bin/python: {{ '✅ СОЗДАНА' if symlink_check.results[0].stat.exists else '❌ НЕ СОЗДАНА' }}
• /usr/bin/python3: {{ '✅ СОЗДАНА' if symlink_check.results[1].stat.exists else '❌ НЕ СОЗДАНА' }}
• /usr/bin/pip: {{ '✅ СОЗДАНА' if symlink_check.results[2].stat.exists else '❌ НЕ СОЗДАНА' }}
• /usr/bin/pip3: {{ '✅ СОЗДАНА' if symlink_check.results[3].stat.exists else '❌ НЕ СОЗДАНА' }}
🌐 ВИРТУАЛЬНОЕ ОКРУЖЕНИЕ:
• Путь: {{ python_venv_path | default('/opt/python-venv') }}
• Статус: {{ '✅ СОЗДАНО' if (venv_check is defined and venv_check.stat.exists) else '❌ НЕ СОЗДАНО' }}
📋 УСТАНОВЛЕННЫЕ ПАКЕТЫ PYTHON:
{% if ansible_facts.packages is defined %}
{% for package in ansible_facts.packages %}
{% if 'python' in package %}
• {{ package }}: {{ ansible_facts.packages[package] | map(attribute='version') | list | join(', ') }}
{% endif %}
{% endfor %}
{% else %}
• Информация недоступна
{% endif %}
🎯 КОМАНДЫ ДЛЯ ПРОВЕРКИ:
• python --version
• python3 --version
• pip --version
• pip3 --version
• python -m venv test_env
================================================================================
when: python_log_level in ["INFO", "DEBUG"]
- name: "Уведомление о завершении установки Python"
debug:
msg: |
✅ PYTHON {{ python_version | upper }} УСТАНОВЛЕН И НАСТРОЕН!
🎯 Основные команды:
• python --version # Проверить версию Python
• python3 --version # Проверить версию Python (с версией)
• pip --version # Проверить версию pip
• pip3 --version # Проверить версию pip (с версией)
• python -m venv env # Создать виртуальное окружение
• pip install pkg # Установить пакет
🚀 Готово к работе!

View File

@@ -1,558 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
30383031656136373366313033326464666239313737376637323139646239363532313866366337
3965323764326333393261323339376564323135353137620a383034623165383739386331653134
35396462313863663136313436646265313534656335393232623064366337356634353632633538
3530323632393365630a346338626363656362363632653932633936346464643161386138623535
39326539343165363062616464656136623531346131373434346338303366323431616533356234
65313537313164326261346461613134363837373336636333373964666632613535303462306632
66313665653738653938343939333839336332663465663130303762633438623963663335326666
32613465663938323532353664623161303462333531626133303862366366346232343163396232
39633532343264613462326534633230363764656236396161306264613730393131663633663463
31383662316263623038353761626165363233323737333561373833636135376564346536633735
61356632383565303331336234323635383666343032326161303931646530393938353432666165
33366564366135326333383536646635663533623364653664353537396166386363366465386631
30326130616538646138616565393164383864353766643539313937623864386139393231303061
62653866316234383435323839623235623334373862643363363339636639346237326161323738
34616238326233613364343636313862393235393030376161326366383938346132313066333039
64623834616662363832393333613634643932633864653437373533656565383339653530636362
31613864373234383763316132653263666364326362613436613735376561623064363039613532
38643838633434653534643265616432303537383733326433663538623135626339326231396533
61353432306639346136366539636539633830343838363438326233353132303036636131656534
36373466356239623964653334396539356538636232316263646638313963623733313735303439
36646338393566306235353034376633336331363064613563643430353233363731366434343434
61633563353361373735646364336434323362353433346431616335366134623365303435376435
30376165653933643532336131326438316331336338333734626430356431326664336165356139
65323734643263313563346638636534363462616230346237303330303033306366313432323835
32393532316239333834653861623530646163393731616135643666363732646562646139303939
66323331346636373234343932633834643636653333386230353337336365643964336139663732
39663631386235383734313635613436323238656365643133313034363061313131373262343635
38316537386564396363343163663338393761616363643535376136366630336430336566393839
30346134646131383733666433303539313762336136333330313662336163373632353866383539
37353831386363333432663035396466383736396331363839643264346465363435363638303962
31656239383030356433663632626430616332663239333863626131653031393863323139333837
34353564613736363866643161386332326335303866623136363865636264383561313338323464
64363239613730343335323736353234356333313132656161653462396633326237663738643430
36346365303839303861646464373366626366356336373632383532613536656165643435333034
36646230356335653632393532626561373262323332343533363462376138333564393830323665
38366430343063326565623366333034643462663436376538386130303438633138356439326431
65383963653930306235356631363163376162353831626262343237643034633231633864653736
61363239636130653464653239386363663339646331613865383931306635653764343835386438
35353864333434663264626431636331303736353437386665363033376464623730386261643836
64306634366237313733366463366162613137383232326538383863666462373261376539633639
37323463303438323137613166326263313432346533656132616565323664376332336361373862
39363739353862333331383731633962643233623337613738663538303064393764366134373737
61316666396564333935616664303235303032376532343265396163633836623965373936363632
64613361643961363261636534343734616433386364666130353537393231623831636135396265
37303639643361326665313161633835666464396432656539653563366338363633303034613966
61333732623334653833353033326139396263626436376366343263656232323033386162343133
32633131623534666663366632313530346233303631623332356364666634643263323562633535
32633261356462346530623264326331356438643230313261623036653336376139613863613831
63643932623330646338313563333266356463373639366537373463616239353638663733653530
30613566613364353564663761313432363136613634353934323265326530643039366331393837
63636136656435306366383438313264346233623237333433303666396131346466303737323566
34386334633232363333333733333737636534363832653663396664333736383438626433616434
63303738326238336365363664316338323033613461373038383931373261323066626463616639
38353964663663346436326536353836376461643930313034366534336263613563333932656637
33383263313735373233323161623135663261623230633636653034613537616537623433616233
38623937343834353033363964643665616563313366373430653161666462653930323930616232
30333539383832633038626634653134393538666666363736373730623134353264383064653330
33356362316631633538323032373831663932323466656362623530366531646331353139366335
63333832656235643431393162346233366265306234333435636361636464313862366362636666
37363663333164613334666163343033626361316464633465346434613833306564316362353864
63323834376432373738326130333136643665376638636665356534353834306536336463613430
63313338313961653234306662616639363061376265376561643934346232626233363862363237
64616638336238623836356238653037333964396630373539616664376261663038646334643565
65626439396539366231623839336131353363396238316630376537666536396565646431343162
34313335383461336131623765653836643939633439353463343531663339363366353261316436
33656330396133613361343630666163613534323963316234393861316537613362386237623434
61623736303636303230636363386665383435383234643464373766323366363233343730306639
34313165343237346234653730343631623866633063376165626538666238333763616230343335
36363235343361353964666665303633363330643230303865323039666263336431303566306238
32316461646438343964373463313563363634663238393432353235313030303030653666626334
30326463336233386365613930396338613733333835346362313939656438653564383363626432
36323662336662353165653931656439323061306534323666373837363736386166326537663662
34356162316537313039313132383032636337626366663235336532636531326330376466646637
35353737626534316431376534303162396435643538643138326430326534303530393136663138
61303530643732353138313230323833663862383534393336643334326239643339353631376561
66343263616537633233633635313939323963366661663363306164326334333935333061653866
30343465396266636436396666363731333932623338396230356336653438346330633739373930
30343734353463383530363330343033303961323765386438303432636562306262373038386135
62623466333261613062306636373230656333306265636366623965323136356132653739666363
65656438366533393933353064663036386536373662626462666438623663383866616566613564
34333739336665646463363131323435613233323138306533643532326363363132336239383939
63636239663834376465653839613336656137653537366664383666633631316665356337306562
65653363393938363366353237613337653635653336616230333831613939333931366664343531
32613336313561663639643431323531306333666663383563616338653535356232623365616439
37646434393032353364363532373531353732353163393261396430333234643961353835326164
61343465666635333730353265346535636639313464633564373930303133393164666263353366
39343532656363343534363363353665353939633232316536613661333039323236383633633065
64393261633539663764383738326666663664363131386433386233633063376239663336333764
63393633353530303132636262346534363434373730383261383165376330333232386232623334
30616665393535366238666662626665616531396162306261333466393038323236363961343461
63303136353764363635666437396561363162336237636532316630646166366265356662366532
33616134323364616130366337323237353934383730356638393265376164363636396439396663
36323863663562666131653062396136623961343831383461303162643639323934343163646635
62343866393536343963623234623063383063313866353831303735336132376233386138626436
66343734346461313536303539633362366661666363653932303062653632643866373161633139
34373365313965616130303339353736643232336263313339653032636161336565613061383463
38393932373833353036643937373261336231613932666133376531373561326661356137363764
32323431326230346334326666393739356461333432613739373539353130326365616431323439
34303934636335623132643832633734353866643561636532313363633265366235353136343663
30343130366634656466333065353534643536353939626562383538356331626635363932353339
31373566633634356161353335326162363463346333633037386638303465353134366466313064
32666239653838343763653839626230643532333061386437343765373962616361643438306138
39633864396366383361333834336664396663653830626661343630336336356666326330346564
62663961613331613735303535623239343363346538636264666333333965316566643839373266
35653938363733336535626639386466306531333361366339363336383864613237613930353430
66643132616231323036363134383230656666653636636630636565653362343039383730616563
65326165303236316161656263333738353532616364633636333565303335653039373265363930
34663238616163393662653639623435343764363737323035306238633534316234646136383866
30366234636139643539653033653434623462626663386161336232643465373933353064613431
39626264306662666462383863323530383332623438613239663334373632663930613838396133
63613634346239363738633532666139393066613236333339626166613033663066376462326363
64616631373663313337653764356434616536643037303039663264653435323262316337623562
62333735373334663830626161643661306266333836373630313132346566643735386535323833
38393561636338353130623834346330666435336437633865326161316665303665393536643363
37396563653830643536323362663763383735626633643631366462363466396165313132643532
38303638353336363463663135303431373531383130323037363537376238633033656330303433
32643039326532346463303639653062313634623565316130336636353566323638626231643966
66663736633066363631613337393566386566323031353532623331653039643366346664333231
38363964613039643633303032396664666231613537323839613061383861383034353930633730
62643635313336393334303834656238653263373333613866336533393436393236353963353437
35323331636336643136633334663264663934636531633234613637353265363263373461646163
36313936623165396631353430613564303839366630616132616335663264306264343536373065
39393166356163633236626138666233313435373134643561666631326237373632663937633034
64323435623065396636393334306435656566356136633830616364363439663164643563363937
30653839626661363562373938383837613663613234303739306662663535333765353763383631
35616337326530303662626163656162363636643435613661373933663565333061653034623235
63326131343863326535633135323065626335633839343031303735636132316637366334343335
39663865396533383439323830316639653732343264633931613161333732376138623434336533
65303332333335613239383536646435376161363463386536396663373338313738383230383034
36326436626132393631666539353139303964633132333432323335356435386361663339313836
38373336303966663037393065633337633136646561636561363733643633353164626663303864
32393538376136373861306461343637373535333236643065336162393731626164326263646632
64343863623764396564303739353937656536636235643238383836653332393564316633386535
35623834663137663833323438396164393933616336316333313835316432373331313764313464
37373434366532636233636137303763396365373337613936633534386261636464313562393332
66386532623261666636396435393862613266363331616436353830306335396431396464663866
37356162616365663366653531363366343735363938646262323132623364376162313236653238
33643434396432663861313236666536383766363531343731343530383565656339323261626264
38376534363764623466366635363936356337316161343135363366363161363237346465396362
30613634623365393861323864616239626562353536353964313037613764316261343632646335
37393965386463613437383231363434643464653866633438373634336437303364653833383061
64666663376631623632386164303535393732656239616432363937636239633034313762383462
64393730353333336536626435363231313633626433653330653861363733326339363632623566
62636365396466353163316132316332363238373663303631663535363732336536326232386138
32643563306333383966613866333635326332363039323362643137343531316630346566333635
66366430633835626233353763346165393436663562316637393737313032306130306165613936
34633133396131393464316432626162633731333961643565313734333336616662643163323662
63643665666439616561633330656138656237323465323766656335393938376234336532626335
64383635376539353064333766303031386635303861383631623661306461356566313337333564
35383861303732396630303338303037396263323164303263663331626662656565656635663163
31633637336234366532626134323037643736633363313363346534396237326265303632636531
38343565386265366537323235366432356231643361373134386461303734323465376162316437
37356163636135666131353938653734333430343532383436663536616164306637643132376137
33323034656564336263386131613030633033626536663461333531376436643038326661336633
32613562373061616165393738343433336337353731363238316433643864663535346631376433
38656433373938306532656333373366373533383361303638303031326237633037393936316361
32643731316665323862323736663036623535346633396261646430666634646437336236366631
35356364666130316530346539356639616136326463326161306362616137323934643330653638
65316234396538336466343037396638623865626336326630313662653438373066363562356563
37303035333263653233643634323539663533323236383766393738626234653264613966386665
31643736343230393064616562613339653133646530656664643234343733626562633831316432
66323539643039376330366539663431633430366536393439346136303561333466323032373332
61386261303662633334353964303835623836623934373365663663333036653939306161616333
34613330643931396366613961323837663435303762656237336631366135663230653963643161
36656163303433633634313034306138623063653464313035636666383035356232386566653836
38373033333637616266313739633235636430653964303538653837373862353133306562353437
39373732653335306665353561343834356364346537306537366630346261623965326235663464
31326137303937653432313935313962376537313635343538633038383436613231336434353033
38653764383539373737336231396561633934633031386438656263653265306230353839343731
30646436363162313063653733653861396261383764336535623731623662613165376161643830
35376537366264303463326661373532353436323733623262396365333536623365353939656165
31353263326535643938636163313330313135393335356335343466303865353566373031643537
63383337323461396535333864333738363333303932363063303235316461356239626264306135
63376535323461656365366364623036643230643838646636393764363432376262633264373238
66663364356335623236643964663063643134656162363761383263393863633539646463396234
63643465656461666263316532616162616166646536353261626263373361313036316634343232
65646162396339623636376434313261656532316161653731336364366632633138373136623465
35396235343336316361653361643332353337373266356433386166653539646564303636333666
65383032636236613230353430366464626562383462616665393238383133353236326365643463
37633963333530623434633239333161326339623633626661313465323364666665663163333862
39636663393864643230623966356237613162323232393539363138643165653264313337373532
38343439353231373361613266386565326634323764353238653061613238663865363366663465
39323336653664373036363966313137643632656339613737346236303030386434653866363662
30623566666231393333383135393233373232326466356636383865313036393430663137643964
62376664343834326164313238633361363534313264383132306239363764353934306638646564
61613166623032393537306231363864653339656636303238636439363830313537353466393262
66363139643638363466643366613065303562336262343733386338663533383431666131386331
63666164613031313037343961306165616364613533303362646563613465366462353066373865
64353038376535306234636664393834313966376430396136373333383963373766323063636534
30393562363030663364303332666232376231373138386334653130386437363639326330393339
36383963363235306665663762623530353132626234303262636630653662623435393162396364
66613737626665383433303065613062643230306637353862306461363834643863306130363931
37613838396534306130363737643763386639663030303166383636316164326233346536633265
33653334353234313437636338303063336266613964343935636634633665356233653230303431
35363664626266613539383363323837316433613635653765303436333133313266333336393930
31346535646633336164363930396532633139666462326463633565343136336532363634326365
62643638393562643830323635616562656366356665626665653232333133396263313362663432
39366538353138373439633636626338346233343238383761353839666664323232613537316366
32363738353465653335616465303138376137663532363339653237383834343834333761353563
35306138306365663030323030353066383735636135393731393961373830303331666330366335
34613139616333396139623161326164623433396362663465373162666139376565346564383432
65613132356161316337653966353332326430303735313531626561656335373633626230646532
37343236386534376531666138383736373634323762376436646465306361303333313238626264
34316364383731666332373864356235616336303265383130373365346434383137646562616436
63373364393166333839666535376266613933653335353032633933656633666665363239666165
36633934393263363961663234636134366634353938356639313466333963343363353464653033
35396463303333336161363238393361633131643639373064653531643734646537363266333031
35636533303061326563373564613461376632346335666263623030646232386562646166313038
30386336373661383338626234616633333437396465316133633764393337383839633730663565
36386438326334633132383334366165393734666634396365393763373965613562313565333135
32663765366532343339376131353664313663643033623039663237303330646363613366623830
36613664333834373333343139643163393665353930336666613830633064326431346437346662
38383961323162386162393035373931633938363635396534383138633838666233616532313037
37353231323237326633623730653936613834666562303461663230656565366339303930383635
31623338363866663161626266646639613865333835326130313161316166356331663733633136
63306438336334636636383565383238656537653530336539376133656531393765306562366637
65373634313361373461303439366564346164303532306664616636623061656138363939373839
66616630616233326336336432333932316163643339333639393134353265636364303037396462
61313333613230633764383163656233393637353463313864376536313863636464316232626235
64313333613833626636396437383230323365393764653630393832623063633530666236653962
36393630633838396333343064313063323463643861393936343739653834636234663238363239
64626631396562663562396236383933356231643631623562396332393038373830396231643065
66626165626564343262343165336231656232373764643130643138393862343534326165346665
32613235343839386233326166376131636231613964346235323833656164393961333131343233
30383138376632343437616537373338623163306437333961353761343137373233396234633738
61656139633333353532626535306232633230303935386434353263396661383961663832326262
63323638306134316466326431323633383734383833316165383966353933373137396231616536
34656437323934653134623236363163653566613332343262356138386437343337656536653835
64666665373532363966326234646537623864363734633936326130646432623062393965616135
64623565663735643135366439306631396239336165663764373936316661383434333563376230
33313766396632656165653130323663356331343934353133666338346533383465303232306534
33666165353638373239326438623637323864373062623336303132623837386333613639373462
33386135316662613063656135653763326631326235383632336638353130653061383361363437
65616265393438316164633832646561666536356563653464303639376536643231383036353630
35643836663938343036316637633535636265656164383262396138343631323030353132343361
33656264633735343335366630366630616162613635656364383663626136376639353262336564
62633139663662386539396137343761386666613062353838323530333062306263643161653134
31666463386163636530343965313439653464616261306534653466326564313934633561636263
62306334373634626438376161306633366634626633313836366637376136353061656231393035
62323565373864386439623865626536393933376664383762663663303335626166396665376330
64613864343863333133653666353231383863616563303531633833376235363537336233313562
36303634633538383461636332643661613133653436633438656162653930323135363964346538
31343164343535306463303630613731313531363838393431323231363461343434623465363662
32363165323433383937306163643262316362656162313131333635643039643262653763336461
64303338623862613735336636356137366536326166346233373430346432383536343731303962
38323239313132623133333632333534386132663936643965623437623265613330643264663938
31336136393339653134376161376336363633613433363233653032316535313437323134313163
39313762383961346166393136613439313664656163643262653032393131376263343033383536
30373235326130316438633334666364643137346438323436613563306163303732653261306332
61636231653630323739646661646431613165646261626265326537363865376162323962643232
33353962373430363861616137373862366165353139656236333837636233656666336361353531
37363865363039336331666130663438333731393032353365626437363537633138313230383931
64633764396365313535363361373938376230353432643833623737666335383831313863393366
30333734363036636636356139333437393435366461633764343361393734653631313361383930
34356438356164663935323536353238613164353631333364366464396632313736326365653461
66613561316138663866353165326631313230303735313939323765326266646466363832353334
39336261646130663765633632613062383139663662346261306464613139623232383730343734
64356139326338306664393736613964353063623632356333666132373534356465386132336331
63386438653566616435663230653734316339353031336232396430613831623832373165653130
64373262646438393035343066396237613630346432633666633863343465303364346161623331
36656564623037663265613565333230323534393034336534396263353461323131633666376539
30643766373866393132643737613731343162346431343334373533363164616130616364353963
36303138363235353935386138353530333063663632646638303331653261333634303338363766
36326333613433373134386436326162313231316637333565643730633062363861613065643739
31616463663232313937626438653064336630663630383838386333623731366235366430373434
39383530353036343936646235663233306330626630633238623837663564353763363064363131
37623564333766313530343737363833343331623539383934346134663165303536666261326136
31353137306162623932396432666162373232396237663534373530306435613138386165616636
65393466393636643239373436663364313839616431346565316433363865313333393164626631
38393234393630653064336665613431303766656365303832623233363038376538613862323330
37323832663065306436326238356236633135613737306366633133326535333630333065343636
64326631353830663736356630326565623964656166623466633035373762646562313138343165
31666566623639366265653530323463326365316335643966393266333939623165386335373730
32616437616262323665363065306563343964353866663734353664626139376538616633356164
35303230386263653331356530356332616535343536643064666133616666346531613131643330
38363965353130313762636138353830306266633833643035343261636562313863316633613037
64313364623231383761373630616633613361393062653936303231323464333739313166333234
36633762376463613739636561393937316662663963366466343364313932343237656336326433
39393437383735306263316263393730383763383063336137643263653663363532663462363038
66623832643439326334313038616235383337323332653661653137363661343461643033343531
62313261343936303639653132316133623938646235396638376533663136373136326466383430
33303234623238313063613431313263303861663534656438333037636236313335363033323530
64353739383361613330316332313137333465346636656237366230316262383430633466323337
35373662323631336134316663653665396535316563333931366133353237663665376238356138
37653433376262313830663936656236646531333136623136623439363935646265663639303832
35313930376637633832356634356361653863616136343931326237373237633930626130366461
65613038343934313466653535656336323834323964356263333761656135316134306335616339
65633931326333383464666663623938373137393734633934376133636135666564363534663164
39346539386362303238343430623666393430363962636337643631313766626430313636376166
34646431643131343461326532343034313163646565623131346639303435333731626364393337
61653536653163663161656661353531653530383065386330366431353863616233393265663061
35343630306261333437633861393635383462643135613836356566363933356230396265323131
37386431326431356462363734366263613439643265636535616537373035383262613934663663
34353265653865316337396333326664656639316365376535323738663335396165663330376531
32356337636635643365663839653164653239643030336364636165663564653137303034336532
38633963316664376466383537306231383765643533383237656163646462363465303764313037
30333765666661363961336233613961356531393865393430643938613339333165316562623034
32353737353161663764343763656265653762366332613735393338626264396431383061633139
65633565653638366361346564323866626566323839393365376662616365306336626336326333
65616636613831346131393135323066303462613336303437643438323462326161626137393130
34666231366438303737626239343135353862633866333533366434663837656133333131626536
38306439353535643363313231333834393161316634313332373034313938353864343333386164
64623133376166383666383339623465336163633130333530383939396233306430613737653737
65326561303137316331366632633735646431336538396333343731363139346563383332383231
61656632356339626236353932313033663138356663373464313734303165633063653833303361
61643765373236633533363961383834376563303863663732313435663138316335616335626431
34323662383662643536636561303438623464323632666634656137316632656462616138326335
62663034376133636136656561376533646435393938623936356466303735366564316137633765
36626238643765303437356238666166656462643737333939336261346437616564613132373033
37363834656636663331633064383831623966373963633137666265306430346631626530343430
64626566633237313737383836656661396132383634333831393732393732663665383565396532
34626538306461353265353465626263353066646334666439343963643137633934383434636138
35666564393035326133396238313862396536343466313030316566323531343437613863663935
33633230376135393634363064306531303266316131666532316135386263656237656232326338
38613564613263353031363766333732633736376230393634613064626434343562623164616133
35363837383235343734353365353365376663616134643966363331356339646261353863643039
32313331633364383235316562333139626362316662626631643863653237323765353261323431
39316135646665366634356436663730666661663133396161353566323239353463336133336530
33353734316131326662313365373536623434353261336530666363336561323036643661613462
34346166363363346431623139343931623164626135303038623134613033623939313539353838
30393430376161306431633132343039643161376562393637363338616536636538333837323336
62396565333261316163636336306139613935336666653833373666346162623962633938323237
65383435323036643664393635626233663930623730313036396132373336346462636235383131
38353731323562613666373534616366346333646535343332353931623133333931363763313433
32636534643736363066633535356562663430353335346134646266623761646264623463373933
34373936363262626230396363306531633465363966333438376433323366616436363030623032
39643836656336623338373565326533366366646663636136316362646136613463306666653433
37313366353230396334316466326238396165366262393161653334636532646635656334373964
33393133363265306434396332313636663165303531623462653262393062396261623638376364
31393033313731353134326163623938636539313737376134383062613633306466366236373862
37623035303337333437376165303563386536363964613663653336373465636433326539396166
32386166626432363062363566326531383038306464376236356166663837313166613032663237
37363236613662343037396161623738613738383533343831393533663665653563326637353732
37646362366533396134376561353234383234306632316636333433636337616635343534623139
66363833623864653961646134623137343933653064346339623736643162643366386631643762
30363766626434383531363261306563393862323231663131323131646332626463393965356665
38626436303630663132353164396131386232663565656635366134643238386633396534303864
35363538646461386230653239386436393865626235343137333562663730343937323530346139
37376136323736363964643165366662613738626537633065333631613163333436366163383865
36663161346536383935383833623238383834343330366561663236313363363737353263663033
66636638643331393335363761303533353633393534316332363736646361643630353466666366
34356464646431643366343631646339356532326562343461643461663863303937356337396633
39353839663761393133353262643535313536313761386636633939636562353166393133383336
62373861316437633430653438356562623138386230613238306463346131363039613265343462
64336132666364613762353661626638343938393638623337323366336563373436376535343236
65643835323733653563643639383634316166353138323835306365306666633664623834663736
64643462383863633836613963313666633631633562646336653237653562633464373035616365
38383032633532336264303464346266303030336334663934376361623230343234653937393266
33383162356632346164316564343933386264373866343439333966616436663065616236626631
30383431623163346664653833376563626439616333613539376131333663663864376233356533
32613934386336323834323835373163613034323438653636636133623439383265323735393166
37373039616162336565303836663037636466626439653534636236313831393538396138386662
62316366313634393461326337656362363131653564616432616239636364386565313531633265
30383136393961346164666430633631326133366265396564316165346362306338623461666237
63626664373533666263303934316662353061333239356631326232316461623233386236646536
36663064653033343539666135633932333832373564346364336135626539323032353430663635
66376134306639306431653666653230323465346363363563636261363666343939316532616638
32323366356430633861633538633137313738343664363766636434323661623963396561313663
37323563333732306131306665623065366331313861396661343731363866373236626232623836
65376661303638386264643066386339353366633564393665346632643338613738663633353332
63333261303962356635323735336234316133313365643231373738626338316437616531656337
37633235323734636432623061306366666331343235653462666465393531656665633339333063
39363765383635383964346233333261333466393336376665666663393233373533623761366361
38393033653061366532666632636461396236363662333534326562633230633863633231373061
30616333323661323534323237303034643937303138343261356438626161323737313335306636
36646236653036643731323736653263373161366134373230336261633966306539626331643534
32363333356530306365353635323839383065643833666666613861646465383932353962663961
62656639356438643661643630333736643237313231363532663164633535353138653964353163
62326530623864383366383162613961363830376363306334383433663563363064306666656231
35383037646235643035383435363733623632646335396162346230373133306338666339333363
63613466336236306133623338666237313432373333303638303339623530643362356263646165
34663030653861656564356666623835663465623761393363353666353339663234373132616630
64323139353438646261343935666261363739393131663831623339656266303764656563333563
37626235323334313238363839383837336264363533303935376530393664363136316235616138
37373363346139646566343632373332326261393136393537323763633634306435623538626237
62383561366438366635343239333231633533313734313963383631356436663738623165363962
37383134353536373836383834323034323138323238313039656566393135383662366436643936
35303430663863613365366464333930623331303338353731346134653638633735326535623761
39343261653138333734656232303635636464633635356661303939323138393364316630303632
34643535306434353830393863386461633339373336383138363664306534313665396238346364
33633132366432316233383332613932333536313733333638373034313336623535643136323866
62663531376232363935663939616563623033616233316438363633303530653831303030633832
33393266623965376366663364386633663839366134613039623835663861616336613337353366
62643531343730386636303032653766386564636439666133333231653838646564613338303533
64376137613565396339623131366436633761393033343161346232333137656136643635633162
30646436343931316430396663383038356330666562613839386534353266326435366230316339
33306166383831383463646534313039306433313435663033363134613566303531373738643865
37363762376432373361383431336632633238316666636135636232616466383639353763343763
65323362343335633164376264656234343137313932633334663430383661396263323761636434
37643137346263643932306466373066373865626230343765343330653465353535363530313162
36383964356166393734393666636363313733353939323239383130623132373163636366376231
61383535666162643164356466356537343837373036366332313265313366346331396439376439
30343565323864656435646534646433323436326566396131386161613339616531326366313064
31636638396139656564653764643635626537343066313632393937306462353234316237313838
34633239646532386333333136653838666630623033363730343665333335613963613337303637
31616261636235326235383864343539626465346434663736636563346463346431323130323432
62643134616330653334666665613162396264323463366434366431636134643937626265363032
38316539643133656530376635623937356638363861653035356536663665616164613439626461
31326162613133303330646366663930376638613139653563366230346539663237306163666436
31343137346463336133383261646339643330636262333833376237636263326462623730383937
37303739386665376663313566303134336565326330643662653263623333373234363863353836
33333139626335383334633830306562363062343437356561303634653032623465363833336434
36366233383532643634373961376463323063616639623662636264623632336532626338373834
36663263353762373563646138306261343763323230616535666664363636336164616134323164
66383330343036366466363162306134613761653964323963323434333931653661313835396535
63646438633739396365623263623335626161393136656338343036313532323238306538386331
62366231356438393331346362666566613634356635646333643833333436313635373232303365
34633161643433316437313231303965393066383934656430316638356166363561623137633638
37303966653362363361306663393761333634336464663338376137346439633364316261323566
63396534383331383563393161633131633539333135306262313332383634653136663465353231
61646638663039326637643837633430376137393632343338323632313832303132303736393434
32306537343665353430333734383439373037653361316437623738373865346332383363633330
35343664353938316335383635396563303664636462393965653030343433303231653539636565
66376234646666643136353365666265616666356262366334616631653864656434623533623139
31396339633034626332316363303866343435393732353865656233343662653737616337366663
64346637306332393461373162333933303136653534386133346265656333663039313236323965
65643161373066333037323936376365613536343464623764613961656437383639383632656165
63323330663061636534383865343535623362316566366137633162643465343132616632303231
39333961623139373433386565366166303661363830656633666262323965646565313564623261
61363730353165666330386164616338666463303739373665396166393464653536653733326239
61623962663234353864396339623766623561376161366632323961373831623435366433656630
31616431613139653166633439633065303466636539306339343837316334303362653335386266
31333766643061366634353636623265313566613965393566663462346632353837613531333030
33373636333433356532346139336137643565373938613439383164343562376664636630306463
35616464353364643239373563346538633435383065316139343931376166303336656534303139
36373237333835643262303866376663366564353732353434333262636566663534663865323536
65396335656561656632323436633535663664326139353137633536643534306633663964646138
37393138316131616264616239373262353633616235666162306564383838393662663830636532
32623939303033636562356230613063363961313936373239653663613832643334303030333135
32313531303164646438623564363865326137396364653066333831373137343938646533373637
34353863316265333339396330363537366537663434373564303034666132653837313939343262
64353663613635306239323933346233346432653761306163386433313464616265386364336532
61336463333230393033313464353031353635623763343662356162343061643839636464633732
38656538613234353235363665646363393466323534643463323032336466623736356138636461
37356438646461653465346265386636336166666632333065613131663837656262623262346563
61313965376162326361666566623138366433313162363033323862333331633964376363363065
30363962376134633862623364376433666334666537383937663162336637356333636263616364
30316337633735373263363861616634353330633238346461663263356232393533656663656538
36383032343764313434653362623332356431313866626264393935306532663332373436333333
35316536653237313264653566303934323739636663303932353636393832373539613264633739
65643431656330636436656337393062343566633864313638353637313430623365333230316234
63613937643666666337376334643561393431303765303865663665336162666362386338653634
36383736313662326433653462663666303633626130326334323637623137633439343930633365
31613963363334333031326530313430626462363532393563643365633035633436313439323430
39613038396238353235643238383862323831663138316466383530623337343661633563343765
66323363633530326662656134363633646666393530383430376362353237366631306365353964
32333061636430376336323937613564373739306366316631653961383937316437646561663131
37383534616636373630393566333262313239326433343136353764363533363964303939373333
61373265353936333765353235643438306264353261393137666332313639663132626566643830
34376637646332383935353230316163643262376433346366666437333462336630623064643964
37623231323230396431626636663433623136313737303062353237656130613465343362613365
34356430386633376634396339333166303235353634336537656332643161303663376137656435
30393266353464656637616639626231386266633133343062663939346261613063663735656663
30396236616537303264393437653534393762633162396631386162383436366330616634316262
62336234636465333663323065303939386536326366326336643762653136373265663130353136
31333964646335653235656466393736626335616237333530366636383664343363396561616437
35666138353230363239663831373564346566393263636665353831363233643733306435623761
31613535353637363235373434396563613233303137653932326361366465306432346164316661
34393263633038633166383633386538656362323765363236373463353532383862646265353361
31646130636661616633383232356464616638316238316233653337626530343933316661626530
37336539643432616261646336633465653961613661326434333262373837303362343438366139
66363733363537653461323531373331613737316266636561376232643461663363633832616235
34653962626365383237633536333730646632316661613131363835366464313038363831656163
66306261343466633230616265323532663130383237313131353565393235613437613935316464
39636665313162393231623037613837636531323132346330356137306265303930393565396636
66343534366162653361336231363331333761366535646664376664376132343361303634386538
34333933613433626132623735376564366330393833376235306262633038326333343134306364
64616237333364623061623137366563333036633962646462343861303434366532366335613061
38613930626264613132303161663061626238333435643162383136376463353131326132326230
36366461343864613865376262633865643835376266663733666634656166623061373062353339
63346264626533636366653262303233643062326435333137643836643334663830303362323662
32323465666339653161393562303131613664333064326332316361663635313031346238323434
39663966393137313361663933383932623131346264323332383837353366626134303130643530
32663566323237643335646132353663343130316530313363616339316632323339373462376232
33386139306537653834303539663936353534376533313661663334353236386434333237356433
32373766323935386632613837376237353461623735383232343237656432316437333866613734
32333936646133313031633465653166313936613863386366333034376464386564663431323939
64636135383335666336363338343432333966363631303966613465333461336535373132363034
32383639393130353566633431636638656463633262333936396338373866633135353734623065
66306265613362333737336334336339326237333435383435346163663538626232373330373661
33376338666639666334366235313461616566343231343734386332653335393130643534303533
65613133613238306237346232326133396638393232383464353664623266313533323664303861
62356531663936353130313533623062613536663931346565643636663536306361366436653232
62656233613861313062363865313361323965313234356331656630346432363037643632306261
34616433663561623432333437653235386565386433633638633564396439383261326539353965
31656238323666613536646136346339386630366534646330336564313961653434313633373763
33363532313835316537386362613162303434343838656637643161623532656235346639346638
63373765393734336134376239366637633865306431623766303230303030383365323430343734
66623331643433343363376565356534326635336166653766393763383737346561613635313739
61353237666164333939343962643436623336326132633038663365353464353465393934313338
35393364656638646436393037323630646436383165613635623965363430353465356232353965
61353439393566323733376663653161363232623833313432353363393362386266333031633938
35643762613931323363633739633937323437353164303066396132326265353561383835376566
30333234316133303461373962613663626462303266636334623437316438386635363132383830
35323038653633633730616637333366306132646161633730393761386331373336373633643132
31646539313338376534376432653538646236336331316538396362373137623565303332373039
65653836313366326131393066393230653034363732353137343265623464333132373036356662
32636131363134383362643133386132366462343939363832333339386263666132396638636139
33663437366564353338303935643138376665303239336563306362653963393465386239633961
64353565323334343730643839616239316631353130313534336430383435633439633465303136
37336462366536396436386361633364326332323761636239613864623563663363366638643866
64323366353232613963386664313238646263323561376565353065393266303462376138643535
66323266396231396437656237643631373865633036646139373835623238633665613566626532
65333134303138343961356236306661343533656166333565323630363630663338633735643236
66646366326139316465613465653730636630333038306330353263303631636132636634346463
39626433353364393931626639656662663236346461393365653863376166663439336161393735
30376464626364316665613830356535613834643837653434356237396433343633623332323534
65316238643262383531373930386638653661376666653563333161643530303337303134393661
62613365343836363965346139636230393036373931316363383566613861306232393165353832
62363065336531643965373433633236636464353134356631343635353363366566366138383532
64353233646432633730386633666565396664393836373735386265323137623038666266613338
39313161646462343439653039326634646365383865633738333162363365316631326436616565
38623664633263636331336336376333326631643662323065383663336662666237653865626530
37303566663961313931393762633562663661396231343334643566616438386663353337623534
36616563633161323036613430616633376234656130643561633964316232633066323030363765
39346139666664343736383631303662626536386531363637313434356431613230656530323462
66393366653738363465333166323266326239393263646634663861613430666630383036666134
30653964613231363866623532366563623136633363353734626237313339643834376461656161
64336431343763313738393161346434623833323661383565386165396464633566376239306639
33646632633936373431643533393865343965313531616338643237323134663462663432663330
36393038303639613339363231643132346330616433393435393733646232663337656164323437
34353065343137306333323835393636303438353966376333313537633435373862626630396437
65656238313836613736663333333830623934383361326161343961623734366266353335346539
61376662313333613666356539656564346361356239303165613038633430336465313736386233
36626266396231333939633263663837626237346634663033363162333737626331396666636430
31336139383635326436636233623835396230633537373331663631306535326638666138653130
33633734373133316363376339333337306333333465366130303561663535363031366635383934
32616433366632316461396135633862333335376236303563613731333637653466323632653566
34376166633038356264393139356365663939346336336436646132396165366565393830333933
64313262303363306335643634323536316132373261623030383065626363616439653431633635
32323662396232346332343532643366303030653130613564636531316163393662636261333531
39666637303231626638636532373537636131646539323532376561336239386531306132363630
38613066343935386339356335373762336532393862396261366136303361373930383936333236
36633264386330313364353564343433373864323239336263643031656135633866653065616262
36303766366664346437346336353837383430623231396230343965366239316339643964313731
66653335373933396133663333346438643939616665393966653733336536646266363030303862
35666334653661313566366138636134393731366362333637303063353730343132386464656666
65633034366431656130666136633533326638316164363935626433316230396430653139376336
30646135623633666232323333303239343835313539356563363238646431336333633134303530
38646335386661356263333131376661323037636664393664623639353862643534613133323634
36656230626533616332643137333838623166633530363461623964306637323230323563386266
33616438333432653633656663626236346137393130633265613831633237633632316133646136
32636466326233306636356538633362336563376562636635376232353761663561313236646337
31636535346566383032383061666134326666626162333739643666373833393562316634303635
39663936346432306363636666653832666531313835306462656134346132353466613438386265
32393634383861626566333237393933373662383633623562343765326464363931313538326463
65326662373864636431326334363337323362333536316661383233393266346532326165333631
39353161663562373863343561383634613036633339336362663838653236366266383961653661
37316563623861313639613439623634323363333133396362323138616263396633623930626337
65636261346437373733626461306364333837363161356538376430653539633732626163633934
38623966363234636662316462626265626339666630333637366162616131623630363533663935
63343139313065383638656262316661396133376132363336373037303036663132336438363664
63396462366437323737346635303334316335333730626535383461343934643261336338386333
37376132623063363261366565383731373436363735386334303161653361663539616238626530
39373639343038363939313835356462373061616434303937376462656365393439613739663738
31363363396165333937313261386331363163636530373031363935356630396164343430663139
65643464376462663666343032616137666632613733303234323636363062356166

View File

@@ -1,208 +0,0 @@
# Роль repo
Роль для добавления актуальных репозиториев на различные операционные системы.
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
## Описание
Эта роль автоматически добавляет следующие репозитории:
1. **Docker CE** - официальный репозиторий Docker
2. **Docker Compose** - репозиторий Docker Compose
3. **PostgreSQL** - официальный репозиторий PostgreSQL
4. **Patroni** - репозиторий Patroni (high-availability для PostgreSQL)
5. **Elasticsearch** - официальный репозиторий Elasticsearch
6. **EPEL** - дополнительный репозиторий для RHEL-семейства (автоматически)
После добавления репозиториев выполняется обновление системы (`apt update` / `dnf update`).
## Поддерживаемые операционные системы
### Debian/Ubuntu
- Ubuntu 20.04 (focal)
- Ubuntu 22.04 (jammy)
- Ubuntu 24.04 (noble)
- Debian 10 (buster)
- Debian 11 (bullseye)
- Debian 12 (bookworm)
### Росийские дистрибутивы
- Astra Linux 1.7 (на базе Debian)
- Alt Linux P9 (Platform 9)
- Alt Linux P10 (Platform 10)
### RHEL/CentOS/AlmaLinux/Rocky
- RHEL 8
- RHEL 9
- CentOS 8
- CentOS 9
- AlmaLinux 8
- Rocky 9
## Требования
- Ansible 2.9+
- Доступ к интернету для загрузки GPG ключей и установки репозиториев
- Привилегии root или sudo
## Использование
### В playbook
```yaml
- hosts: all
become: yes
roles:
- repo
```
### С дополнительными переменными
```yaml
- hosts: all
become: yes
vars:
docker_gpg_url: "https://download.docker.com/linux/ubuntu/gpg"
roles:
- repo
```
## Структура
```
repo/
├── defaults/
│ └── main.yml # Переменные по умолчанию
├── handlers/
│ └── main.yml # Обработчики для обновления кэша
├── meta/
│ └── main.yml # Метаданные роли
├── tasks/
│ ├── main.yml # Основные задачи
│ ├── debian.yml # Задачи для Debian/Ubuntu
│ ├── astra.yml # Задачи для Astra Linux
│ ├── alt.yml # Задачи для Alt Linux
│ └── rhel.yml # Задачи для RHEL/CentOS/AlmaLinux/Rocky
└── vars/
└── main.yml # Переменные репозиториев
```
## Зависимости
Нет зависимостей от других ролей.
## Переменные
### Docker GPG URL
```yaml
docker_gpg_url: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg"
```
### PostgreSQL GPG URL
```yaml
postgresql_gpg_url: "https://www.postgresql.org/media/keys/ACCC4CF8.asc"
```
### Elasticsearch GPG URL
```yaml
elasticsearch_gpg_url: "https://artifacts.elastic.co/GPG-KEY-elasticsearch"
```
## Что делает роль
### Для Debian/Ubuntu:
1. Устанавливает необходимые пакеты (`ca-certificates`, `curl`, `gnupg`, `lsb-release`)
2. Создает директорию `/usr/share/keyrings` для GPG ключей
3. Добавляет GPG ключи для Docker, PostgreSQL и Elasticsearch
4. Добавляет репозитории для всех указанных программ
5. Устанавливает репозиторий Patroni через packagecloud
6. Обновляет кэш пакетов (`apt update`)
7. Выполняет обновление системы (`apt upgrade`)
### Для Astra Linux:
1. Устанавливает необходимые пакеты (`ca-certificates`, `curl`, `gnupg`, `lsb-release`)
2. Создает директорию `/usr/share/keyrings` для GPG ключей
3. Добавляет GPG ключи для Docker, PostgreSQL и Elasticsearch
4. Добавляет репозитории для всех указанных программ (использует Debian Buster как базу)
5. Устанавливает репозиторий Patroni через packagecloud
6. Устанавливает `debian-archive-keyring` для поддержки Debian репозиториев
7. Добавляет ключ и репозиторий **Лаборатории 50** (обновления безопасности, драйверы, Java, Mono, .NET)
8. Обновляет кэш пакетов (`apt update`)
9. Выполняет обновление системы (`apt upgrade`)
**ВАЖНО:** Для получения свежих пакетов рекомендуется использовать официальные каналы обновлений (требует подписки).
### Для Alt Linux:
1. Обновляет кэш пакетов и устанавливает базовые пакеты
2. Создает директорию `/usr/share/keyrings` для GPG ключей
3. Пытается добавить Docker репозиторий (может быть недоступен)
4. Добавляет репозиторий **Sisyphus alt-sisyphus** (rolling release с ежедневными обновлениями)
5. Добавляет репозиторий **Sisyphus classic** (классический репозиторий)
6. Добавляет репозиторий **Sisyphus contrib** (дополнительные пакеты)
7. Добавляет репозиторий **Autoimports** (автоматически собранные свежие пакеты из GitHub/GitLab)
8. Обновляет кэш пакетов после добавления всех репозиториев
9. Выполняет обновление системы (`apt upgrade`)
**Примечание:** Alt Linux использует свои внутренние репозитории. Sisyphus — основной источник свежих пакетов, но может быть нестабилен. PostgreSQL, Elasticsearch и Patroni обычно доступны в базовых репозиториях.
### Для RHEL/CentOS/AlmaLinux/Rocky:
1. Устанавливает необходимые пакеты (`yum-utils` или `dnf-plugins-core`)
2. Устанавливает EPEL repository
3. Добавляет репозитории Docker, PostgreSQL и Elasticsearch через `yum_repository`
4. Устанавливает репозиторий Patroni через packagecloud
5. Обновляет кэш пакетов (`dnf makecache`)
6. Выполняет обновление системы (`dnf/yum upgrade`)
## Примеры
### Установка на Ubuntu 22.04
```bash
ansible-playbook -i inventory playbook.yml --become
```
### Установка на CentOS 8
```bash
ansible-playbook -i inventory playbook.yml --become
```
## Примечания
### Astra Linux
- **ВАЖНО:** Astra Linux ориентирован на стабильность и безопасность
- Официальных публичных репозиториев со свежими пакетами нет
- Все обновления распространяются через официальные каналы по подписке
- **Дополнительно подключаются:**
- Репозитории на базе Debian Buster для установки основных пакетов
- Репозиторий Лаборатории 50 (обновления безопасности, новые драйверы, Java, Mono, .NET)
- Debian archive keyring для поддержки Debian репозиториев
- **Рекомендации:**
- Использовать официальные каналы обновлений (требует подписки)
- Пересобрать пакеты из исходников ALT Linux Sisyphus
- Использовать альтернативные дистрибутивы для систем, требующих свежих пакетов
### Alt Linux
- **Репозиторий Sisyphus** - главный источник свежих пакетов (rolling release)
- Пакеты обновляются ежедневно
- Включает как основной (alt-sisyphus), так и классический (classic) репозитории
- **Репозиторий Autoimports** - автоматически генерируемые свежие пакеты
- Пакеты, собранные автоматически из исходных кодов (GitHub, GitLab)
- Самые свежие версии, но могут быть нестабильны
- PostgreSQL, Elasticsearch и Patroni обычно доступны в базовых репозиториях Alt Linux
- Если пакеты недоступны, можно установить через pip или из исходников
### Общие замечания
- **Patroni** для RHEL-семейства устанавливается через pip, репозиторий packagecloud может быть недоступен
- **Elasticsearch 8.x** требует наличия Java 11 или выше
- После выполнения роли система будет полностью обновлена
- При использовании сторонних репозиториев учитывайте возможное влияние на стабильность и поддержку системы
## Лицензия
MIT
## Автор
Сергей Антропов
https://devops.org.ru

View File

@@ -1,4 +0,0 @@
---
# Переменные по умолчанию для роли repo
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru

View File

@@ -1,25 +0,0 @@
---
# Обработчики для роли repo
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Обновить кэш apt
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
listen: "update apt cache"
when: ansible_os_family == "Debian"
- name: Обновить кэш dnf
ansible.builtin.command: dnf makecache
register: dnf_cache
changed_when: "'Complete!' in dnf_cache.stdout or 'Metadata cache created.' in dnf_cache.stdout"
listen: "update dnf cache"
when: ansible_os_family == "RedHat"
- name: Обновить кэш yum
ansible.builtin.command: yum makecache
register: yum_cache
changed_when: "'Complete!' in yum_cache.stdout or 'Metadata cache created.' in yum_cache.stdout"
listen: "update yum cache"
when: ansible_pkg_mgr == "yum" and ansible_os_family == "RedHat"

View File

@@ -1,32 +0,0 @@
---
galaxy_info:
author: Сергей Антропов
description: Роль для добавления свежих репозиториев (Docker, Docker Compose, PostgreSQL, Patroni, Elasticsearch) на различные операционные системы
company: https://devops.org.ru
license: MIT
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions:
- focal
- jammy
- noble
- name: Debian
versions:
- buster
- bullseye
- bookworm
- name: EL
versions:
- "8"
- "9"
galaxy_tags:
- repo
- repository
- docker
- postgresql
- elasticsearch
- patroni
- system
- package
role_name: repo

View File

@@ -1,31 +0,0 @@
---
# Задачи для роли repo
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Определить семейство ОС для репозиториев
ansible.builtin.set_fact:
os_family: "{{ ansible_os_family }}"
distribution_lower: "{{ ansible_distribution | lower }}"
os_version_id: "{{ ansible_distribution | lower }}{{ ansible_distribution_major_version }}"
os_version_id_rhel: "{{ ansible_distribution | lower }}{{ ansible_distribution_major_version }}"
- name: Включить задачи для Debian/Ubuntu
ansible.builtin.include_tasks: debian.yml
when: os_family == "Debian" and ansible_distribution != "AstraLinux" and ansible_distribution != "Altlinux"
- name: Включить задачи для Astra Linux
ansible.builtin.include_tasks: astra.yml
when: ansible_distribution == "AstraLinux"
- name: Включить задачи для Alt Linux
ansible.builtin.include_tasks: alt.yml
when: ansible_distribution == "Altlinux"
- name: Включить задачи для RHEL/CentOS/AlmaLinux/Rocky
ansible.builtin.include_tasks: rhel.yml
when: os_family == "RedHat"
- name: Завершение настройки репозиториев
ansible.builtin.debug:
msg: "Роль repo успешно выполнена. Все необходимые репозитории добавлены и обновлены."

View File

@@ -1,535 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
61626566333639636638346163643739333630613961316565653465383138333264656630663961
6463393931393037386131356166303633303337336262370a366632373533316336353432323837
38303135316264383233663635643965636630363738396638613566653737326463336331663962
6338383464666366360a343836656262326433626233663866646565653161613264393134613165
30366535303231663234646635363263633863633339663064393039623764396132346639373666
63333662356332383437663861363939643637303334303239636230346437303736383566303265
37323136383636326463613339633962616532643335313036376231353439646133333938333663
35383131386561623635393661653235386539613639343434633639326538646564346361373535
33383637386330633930363535313737343134366566383734643433336232613731343361666435
63393836396336376431663335666364643161636565643763356366663833326534653963356665
61653763633966643937326231376239333164643564636466646539376437386437633137333033
32346633346331306561333866633135663633303633353862613862313736643262376666373935
34393133643239333239323935616333326631326233663539363730656338663336366231633735
38323662363563333866366635373938396334633036653930383937633466363539323833376364
63636632636135353262313034386666646535616363343063633163646234326130626163613837
34613136333561323563653866373838356638633766613338303764663031333635336664616336
64396662366633356662323533643436663465323332313564323333336430386563636661326430
37666333383263333235623930663765316663333365653733626334353235346462313561616664
32323061323466323065363334633235656135613833393761323535643136383765396530376664
35623836303763383931323562316461306530626536663033383237303232613736663862636139
32363130663534366362333930643035316535303934373338373364343533636564633837633865
61663938663462663135386161663039616434623965623962336434663739346635663239396665
31323636356532386662376235326465353262313966323433613830626135343566336661396336
33313663613361356162653635386337616566373864663034346162653535353166316165653161
36383236303834633130666231633635376636653462313336316530653461613432366335373933
64323436653536326636663632386363393034326461383166386630643564313837363332333831
39303562623638336563653731656365383130353164383137636266653264373636626430323062
37303636353936336238303831346461326232613230333838316364333636363434656631366336
34396461663737626230636539393431633138306538623732316566356334396464643736386633
32636632613066313766386664663333346330633936383637376137346465343336396335663736
39383865396538633266666133633932333032653837663535303939353539316462663035373139
38656339303931313436383935383362366362323833303137306434373039663036323231346235
39386436333035613339316637373138313633366134366130356236313130303665636466326237
35303536313431653864343665396631336436373537316263383138646361363865313466326466
33303833343762386536663865346663323262663935636638326231343232336238383733623335
66326536343163626635646337386466393631393831323565653438643961336238333235343534
38326462633734323638303638623130356564353637663430646236623938666533363437336163
37613966323037366563633331313865313738393334613161396363623466633230313665633639
31386137633537643663303331313466633433363937656262336662646132633437326265666161
65383264616136616532633636383966666230656262333863633763383139353332636133623965
37303534626432326262383665613365343734306530303130383731326663643738373561363638
34343632623032666638663330643638613537636362376363643231633464396431646332393566
33636363396162633639386331663662656337333733613535336630373132643332383035363863
37383336323734383664663732333864636534313938356531383933386435343334326336336530
30306134626464303565333633373564623563646662623262623734333364306466326464613838
34356134373439306465333162653735323337306164353835356332613839353334663036643663
61393061396336376638373163353565393038333962636563353433636336356632613037343834
33633034303232646665383562666638656339396532663735393631643561643832393137623663
66643839333234613864333965623232613766376137316265613462643437346530356434313836
31373461623763303061623434326433303361663930363135363138353337646638363537663733
33366630363939643538323837326362396137306134326131363661326664613437306432633565
35666535393365333932333735646333643630303461653362353166346334613534646334323363
38643934373334663063646362323333613766663638626635323766656338323536323266656534
30633735343261383833383563613937616565396663623334333662386365316334353730663762
66613061656332333763623661333563613431393332313435623662333965643963366636636435
61366238353065663166316536353835373663653661316537613535383631623835373762623639
38633664323434363030306137663966653936636163643332396638376666393631333832653234
36353332393838653664643735663462623130663339396137663130643463376537633763326239
35356661373761643764373363353663623563613834653266323538343632363538656463333966
35626436323564646438623136316438616437623538653832663962346463633539613065386164
64356666623538383763656432663535626537626436303564373066326132343736383036393838
31623534636464366663313562643661613062393436633133353065333962666232346633386564
37666232653034313531306661663134316263316439363564656665363564636366616239373234
39333862373732373830353131343936303533653666313634316432663362323463393338373632
36626431333635653538333065313234353331343662376332623739653361306130376534623636
35323733326134643761313662373365303862333638366362383964653235623331376662383366
31383962636566313431313035306463636565666538663761383433623264343436373331313233
35643337386464313830343434313965323033626633386138393265646464333434323932393538
63633331326462383465343761326636383166376466343738396261386532303032323838386464
31373963333231656332396339333763623461383538386262326361653338306639653237356235
38643435646531663966326639393837363234363462393930643233316231363135323635323061
64363037653533346265613236396632376562363161626666333637653138333837626438343736
64323035313663666266386162366538633338313334316433623234663437666438306562303964
38303537633764383239343639616534393165316132626336393466663864303330366237396635
63643862616636396562333334616361643436656561643563396131333339313464616432366136
34643536336533353338323761323634303565613433333630316638623861373930663830303438
61643864663465306530666666643761653334373638343038656164366433373563616339626262
62613562316335346262303831633235616466333035383666393139663938393636303131663031
32353436643030663163613830623738396566326331383832343833393336353234303935313531
36346230323432393736396131643533336332633136363636663736356264366533613532623932
64363963613066313330663133376233633161386337656632376261626337303232353236313666
62306532653661663336653661393730303961346134343439633865306639353963313066363433
30343835386664363539386433376135616237353065383135326239386164306562636635633866
36393737313263323932326330623032646663303832623166396466643932336166643662623962
62363935393138396133383062393065396632653462636264656361346535363637343736653261
31303939383437323934613035643534346637636339666335363234313930656161353138616637
34636337613863393666366166396134633861633666636162643537666561373938356334653536
65383634386364353930386662633832633266386333633038353938663565383061343634623664
36666564646539633262623065313863343562386536343865303565373661393164613763623437
61656339366532663237623563666635363663616562333135333438336662653634393437643234
65303232646363633433653336653534316534323865626433663266616365393434623132393630
61363233393731383563373566393265343436623266343865323862373636666633373532346566
64373633626136623236363333333561383330333035333835636236303138616164376631333535
62633265323562326433646564366333393635623366353364343031396431336132393561633731
65633339623364346664646134616337303231323739633166636330653135303331383734613838
33343036353333616563633061646636336463306639376637303037613832653264353038623036
61306434396666343761373064343030646239653931306239336233303565373539356331323435
34336132306161653464343234366663333861336438393033323966643062653430373366616539
30393435373430343733316464336161383637616631323638373034306639383461663132386431
33616531376232393330366339323665636161373535656639363136313533653162643666316361
65373362626537346537623430373466633231353134316435666430336336383237656135663063
35396337643363386230343664656135336462393139323564643062343363336436353037643630
63623732303730373665366261373838303463383965343566316336663831363436643335316332
30373664353063663432303232336338363231336636343939633839323865656161313364313363
64356634663361333532633862643435376462306330316262353063353731636532343136623134
34633433373465306232663763623238623434633938623261663232316263643466303832346539
34633430636131393664306330303131353862303165633034386366326536633236386438323937
32643037656134396331656237653866313762633433386162396539663939646238393766666434
32623238626536383831313033363436323261396536313164373763383833363131363939343235
38393733363163633636373837366239633631646530396434366566376466396637343830633139
61383463653066653238616566383636636562383731326364643233646533336332643531336637
30373839393538313030313232646231353238613232626439613564313464373830363230643666
30373731383539323736313230393834636535393931326365383636363163333533633636336232
35393362393162383238376233303461393731333339383162616563313364353464343361643762
36623031383237393133663763363539393531613731346466333266323830333162373565346665
32376165363131343762663164343239383037333163313133613436643034336565663731393135
30363565653630656232633366333332653533643035623438393362306231643862663434363333
32393332646436316134363634633861383337396531326530633237643130616433623234613433
61633265393433356163643435393131383930623764313334313536333061363039666239313562
66626264333831303638616534656265313862653237653837346336626462363935643264636135
39363633363731326432336631666362616461656234323737343834623562666136653135656461
65646561656239393831636663346132333164623235636233376437386530633462303134363564
66336533303762316566376536306165363237393165363562373162323565373036393561623730
35663633373163646362376637643534393335313565613533666661303261326666303534643930
33346465363966303831643664346336343862366231623438343838316561303633333465396531
38616330303038656632343065303164376530373062383165663736356463393664323962623030
36393235616639383236613266663233333738376135353162613161653764623438333662653639
36623061303066336131333830366637343631366438633765333966343965613435616632373935
38306465656362343738343330346466613365353031393864326666613836303132626636623934
62666632376562313532326132323266376464336438613065353835326538336332633036346564
63663531313832336431393563393562383632656338623831623733623132343435393134326630
34303761356639373036326231313530393932323362313330313837326130396564323437656334
36613036343535396638633162313439643230353835376661666233373261656266326134663663
62326563396362313835363366383236376632393430616165636134363065666264333365656533
32306661626562643235393032646563326237646536396664323730633531313562376133356633
62373130363735663130663137313831666566396339616363353861313762356137316534343933
33666333373632666434373534356161376434333362656233623339626131323261333861363837
65376661353639323363313130623634306633326239363262396165356366643664626534633866
38613936383439316662636336646262386465623731393230353935666539393566373031636238
35636334613133356234313033386534336164326362303263636464306434643463313862396264
32323036373366356230333363333834343237633336616366313961643930626632663939306137
36616361636232366330376265383261633331366136653966366663393262323538393432613838
64646434636264363833303563646437313138653062333165666666653334356234623739336533
30633533626637303932393366303832323061366439643439646135656334386363663133653739
33653838363739633833333938653130663834333235346430336463623361643265313461616464
34613832363737646436323966633063623038316130396561643363336432366535633336636331
30343235643531383832346530306332333235353366666665373838326538306235653937393631
31656364326164343939346532643832613763326332383865633961356538633231616638386533
35313132346332386666663666363434663762363530353063656630353164663431396665306632
61666261396231363134396435616662303166636534326534656230646337376138303162306233
65333334636564303463633764363630666637323161373139613236663866616235363132653461
31643835393339336138393064303865346138613638306666356331376336633961333234666434
39326335313861373761376666393166313039383138616639343261663866626333326239316662
62343333623663316237386262636530333032376461336533646561663932373439663765663137
66396361636530633330633135366665326535613035343561343539633862333632636534666631
32363034333133653931336431303736343834373536333439613661613035643465636663643862
36633530363766373161633064383161653064306563323131366466396339636261323232613334
31303764333435653532306639336331386630383037393339646564656563363738623338396366
64343439353634626134333463366562333736383665393266663761396130636535616232343037
38666665333531633938623661373231336162336565623362316531373961383534633436336339
38633533323433616461333434613132313432643666393136663163623861653763363932313931
33353831333238373131373461353332653664393263303536373634643966333632663933326562
32363134613364363035336265633031383361323134316664373061636435386530373539393964
31646230353738663932626565353961653632643033616263646434663431333334646331643637
66333465636362396130386338393562386536646265383563373937346562346232383938326434
63663130653233303161356164393135633738323365393061333435653535613062613966386334
63643264396438326136613030613065656533386465633538326261336664623333663862386630
31346165393435643262363461386238393266353739383361383832333934613838396362313437
65356337306261373764353162306637353464333661663632396431366362393266343230363138
61333261623036313331393566383930663732363235363436343962343234613736633161326136
36316130383737366537646463323832323665623064303931613932363663663836616438343630
33386165343761333564386237343339633334363264373331653031303361323862353739653437
38626261363338366138656661346465633837313866663866643730363034663963636434316466
66303937613364393330306264613565663563643466346263363033373766356564313330316463
39653365386465643735323636653931343231303231313839336435343937613838366230356131
35356634633664383433353331343166363865363062303165626630393336336662333835396134
31393538383066336231343265373332643765343561636562306330616238323362643336343561
39323936623331353566373765633365306662613764386138633736653332353630636530313634
36336138373637653463393338346631396566626163633464623666336238623266306539653863
64366566393733313536323661353031656161336464333161353431343663303339386133626666
66643461653966303435623935646662323061386439623531326332646235313065373066623738
63616430666363643630376237616432376438393233653239623933343664343239303133333037
66346335393039666231353135373630306132376230363761336461363632663865396639393038
65316338666262336263633937386433613636303963616337656365356335326530303833343466
34616136333039373137633364613034636665663038626234643666336638663064386630326461
66393036356163626532353839643433386436643831373339306631383335373538333736386337
30386539356536633632383732316333613533646432646566326432353862346133323137303931
62396564623238643639393964623235366337653231643265623865373463323131393961386433
31666132653434633366623639336266373563306332313436623364633638336236333031343830
62636363393565643536643933316138306337626138626534616465633636653538326233333034
32623036636638353261636630323434643836343432653438316432306666373639383265303166
64313763353436633266396662656432653061383538303134363762383834646436623837646135
35353866336234376562653735636565393838393061663961613866646332363536663462663432
37656461333762643563363936636231643966623261663565386264376566643564396335386663
31333339636235633733383536653338313461346237396664353236316638653538663137626333
36393565623432633065386637636164306635393232663238663063653832386164613333343636
32376637333961656436353338346338653337353666663166663834656362616365316463316431
63646664393932373636323864666366646666626235343264643464326630393166323564636264
31333931646634353062323863663133343736656133353537326536616532643633623864333033
32346531363836356132613161346263313439623535366139366534356435336239623066333965
34373231373333616264393864633734656134336638313064633565666338373533643861366235
30333730393339656165343031613338303531653261326430626535666434373837613035303530
35333038366533623866316564316137346238633134343066363861653963616335393866653336
31656336383035343163333661626362336133303366353666346537363835643761613732643836
36626538383036363230326538666230306539306239336534303665393064313135626136383466
37623361386437643362303336313862373832643631343638663836633930616561313238333634
63333636386336373261636634643265353030663535373430623439613366636631376535663034
64363132313739383964306564613834666432346364373965343331363035336532363264653637
35396335633831396233333763373139343462336636663361316538353364656166366262643465
62303438323866346564386335353564333135393634363265333534356363326330383437396634
37326431653533316362616464653631373965333062616164343961626430313931393334383033
64303664333161393161376536613765646435663237303939613535646530393833363232663537
33333666313065663238353737623339616666353061616431613263343135366335393530383764
37373435623361373135373635373138316461666464303838306364626634353431363964653638
38613463356437643466333133373030363631623139303939366230386463633963646139356638
36616664343266306636656262366133343539373161376237346237616333623235666365306561
36343339343961393062636666396437386139393365343766393362373537363633643331643135
63666465323036633833643663383733393865393033653165353638646366633236373063313430
33343435663936363365393939303736653637373761616335636537663935326563356363646663
35663737633362326231316532316263353366646533653336303663656339396437323130373832
61346564333930386534326437626633633035336631323264646263326339613736356438623263
64663863616339633135653833656431373538623238386130313566396433323461333539623735
33373332373130393861633732373961303763396137393065343034346132656232633566636432
31623937633139643366636133643636333135393537646239373763313961643765343435373531
61376132373564333731356233353738626265646237336432653732313132376538343035663835
39643634323632343734623038666432393966363833313239363464636564353837313861623135
30313330376661323232393031303035346566366663626637633837396139383134356130336166
32343531323538313035623361323663663863313765653764303966396361383533326330356536
31636131623561643332633738643639306133653730396634653866663239656565306366393038
65653638626630646431316363653236616161616437623738653838386631313332616235663837
63366232303962633736653865616236663736356539323537373937373831376436393136303636
64343439323739643562343533323365343861633836306531333764353130663034646562373138
38643262363837393138363164646634356130343238386137653563623064363633346634323933
39633263333936636231653533643837663134623930373163613830646662666634396532356462
39666331353537313465386663386666633538653237373364633366613732363736613365316635
37653263353666353932306362393536376464333362353539616331623434313664663464353166
37336238663631326435346463623530646261663030323061393861333839393263653736326336
33376639383735393137623234623362313436626234336439326139373230383632653861316565
35353038346335623235353532346532336339363366353263623432313636363131383939393237
63373265643065373931333637636230333638353537343866613439356630356666336165333365
63383631313234613662653137393262366239396435346531313863306363653964306334636436
34313939306363663037616565306539626234353139366530643333376161623564373839653230
35333835386561656233346564613739616663366562666237656361653863303737643565653466
36356638646630323162646338363139633336663030393762356539623039343864656436313539
64316366633636353434313231303263353463316437386230343131633038333831376366396664
65313630353665643763316335313435313666333932646431303765653764373064336538636365
33363030303839643334363261373034353539343538303030653932386637653239386263316535
30353733376463313135323862343830393665643831303632356361666439653235356261623632
64353930343961343636633863313632333063636638626364636231323938383834623862303435
33396439373238346565333166656662313761356661643331666666336232613736373761336661
33663739643763346639656566353065613138393764646463613866313132656462616363666332
63613432663037626236663762666262623931623864633465376162303034326536383133306562
34386230393861343931346634356433356166333839363261353166326365393637643136303263
31353134366430343631333237663130316134366561386337306465386137613338323564373834
32616565373339303862313836613732383766393330663763636432313435623839613333373833
61346465353733373261373633393038663235656133613132356661393539373266343565306265
37303133353864333830376139313636643966316462326632383138366138633266353063303134
35386435646330366462363561616535303435386437633164363861633062343466336131396137
35323534323266313064336338353738663738333462333965393831363962616462343833393830
38613231626638646163636265633039343236636635306561636339643561623232663366373432
63626662306361346332333465313061323239333637623735663661616134393461626131613966
61333634313732336131333231343662303466343764316261343164613764303139366436646262
64303939666131633964383739623433376664333061373533663464363934656435313339306236
32386666353230363861323663333333346263333039353432303362313464666530306238666563
61666330393939633164643737333833663564636235373435663434613237613263666130663931
65373835303034653739366537323066663432633137633461656665323132326630323065303235
63656664326365373038663966643635323465303337653033313266343538336436653466313535
36303461376236393938306434323835303239393738306436373063383438303038616636656165
37313833383164383832393735323963636531626463323133633661373362393337353937653739
37346331633839653830336365343338383866646563333932373738653136346436363162633833
32303030373630666263356533393362363234333737663732306335343166363239633631313532
38643039666361656539356161663633643331343461383034656431356162663333613065366531
38613861646462386664323764336465613737333030313562353161386335326636643232303366
65386230663530613434393863383632373438303162313030353531356133376362663666623639
61393336346139643130376262393035663332353165656666633737336434613062646336353639
33623866363236363565333432653937666661663135343030613865336134326666373439633462
66663762303138626464386331373736363637346138393835326538613833366532363233343735
61306461386239646335346130323335616662393036356165646430333263626262613366316563
61346236353235373364333734643332363730396665393039633239656630313762646561616231
64356337353264313538623937393861336432626131336434613237383631363033623932373337
39666637653935643034393662366633383963656462346331336263383836343964633637366534
62626636313635316466663233636332663565663037363463343939343464326132323633373364
36623434616236306332396437376232343634313633623837376133363065336535376531316138
32663039363638643335333064656364303634353136316666373334386463343431326638396132
36343138356535343836363734646363303732623733313537326164633439616161336532656266
63656431623038623035646439356563313530333033613364613062376566663064623263386461
66336566386637623166323631666130656332626330626330333837313731376366326430653866
36393863326230666236623632303134393735363365613536623861366230326437616135653666
62663433316339316561323663653033363365346164383531313961396337653835323232363466
30396363666639666365663665373365306336313062316430363066333362656134666364653463
31383166393036336563353032336539353031646133343039396236316465626365323230653462
38353162643764613336323335623839373361393434626265353732653938363363646662653930
61323833363635643138653837636334666163366366656263323634303438373263313437326637
61353335643963663732376239343634366335313635656638383236373939316664666336363030
61663538656363623132396362353565633764396561666562353463316534643432653965333233
62646261623461353533633536326638336137656662373139373534626330353866303037353166
34613366653837646336626366316236376666303962343534343533386136316363623632303033
38666535663034313937386137323264303064333532393538643739336133376435386662306637
62353238656561363933386430643666623232323530613538363438393864633966353532643566
61366665356635636363336638363736656337383835613437306534346530636365383664663236
63646137636331336636383964373565613061313031376238646532343061353865373734626139
33396238316636626234313464636565643733663761366436373966663262626636336435376461
32646232623836303431303766666337363538613363313061623336323736663139373130343733
63366538333832366365613734373066623238323361396235373066386166356462316265343063
31383237616266643331653834666563633563316264656261663637663965653765333835363166
34343063653663643263333066323734636564623861333238363265373336363661363063333765
65653334376666633330326530303236623538653033626236363639633865626538326138316639
38393439393434653138306438313834376137386232653735306139313864636435306332316436
64653539646330346665313864623666623832353761636439333161666236633065373666356661
61373161366664663634653965363132663463356233633833303761316562393131353031643764
63356530393566333761336363323333663235346439653163643533616139346337623165363263
38373432643864643131613333346365626533343066326238373131356135313636336335663432
65356162663061343464346564353531616338366661613736626132383861356263336362306661
32383264373065306165343531376338373038633464393337656663383465306137383263333666
32343665336466313933393437373463613663376439336635376338613361666430626431353438
35646536643233346461626564303132333438656534306537363961643338346264303863376235
61376164326234653235666239663331313437653630626131373965343739386630306130626330
61373138366532356230333565633139653531643365366464333632656564613034386564393739
30396565393161323165323032343266323439623665383764386138343061346335383463343365
37663738326434653630643634656635386265386464333562613936386439616432663863343434
30346638613837333666353532366662393463383239343132393639373265643337343732313735
34323230343137346339376363336265383130323865313530623136663037383037613065313335
63616235663065323431366430363233616338623435626564356435643635633537306637363863
30356335366365366362313361376364323438643838376161636136633336653561663633323836
61343038313739336232346164376431613532633661636337313664316564323938306635333736
65613162353133653166663136656136633233336330656133383064346136343435323539613662
61643839366361313562333031613665393336666431306538323466303034376335656539326364
66643961393965333163656635386435343265633061633832333434326338616136306630323764
65316430373262373936623735373938323832383065363733306663353965656464613462366465
35313063353664333033643838643061346266326632613562343432303032653563323739393834
30666236643836666238623633376331363161653930346538623237353932356264616636343835
35313936393666346532346437643832373235653733653235613361323138376639336538393633
61343263613736383263646639303833626334623465616462323832396162656631633834373431
34613133343538616264373738393864653165333038316637663566363031353665356565323936
66613465366238613537323162326663623935373130316262346666303466656433326437393837
34353964363433646335313737613761373265306362363531303738356632613563383861326265
37613062326334333837613439616361303761653766633130663938343436363732393463643033
64386434643835363131623664653333353733353935366133306432393639303861616333346665
64646231656436653065643135383033646133303231636161623965616232353862373539333032
39643565643831363762383066663032663035323639653164646361356266623366616364356363
62663033393462623162613062313336313863623862643363663261656339346564386631336234
64613066353365376434313462666239643334643736313036313530326633326664303339336139
38346362393863646361313937343262656438336263376430643765643738383139316163313031
36663736666362643763653961326234303365303131343932656266373165643137306335346434
66323531333236353138323464343435366638383930616432326562646532656265383737663431
64653036613762326236336431373462336136303564663437363631656630346334653136313537
31636339333835343664346163653964303262386136643330653936363965623764626132383564
33326261656133333033626233373333353135636364363963626137623666336338313837653732
65343964343137643135626366376361393035636631313132323266313864633766653338346164
62373635356433373239376336313337363332626234643935326330653331313766326265653763
36313837346464643138616333373365616539616566383233363932303035633338356434323535
32333233326365313135373166373031393534623537303731353363653832313838373031613533
38353365303136663031396664306238666234613262376235646565666137636536343534323737
38353561316564333739326530643137393739383233343836313839653536663739343164643234
33333533656134653061316462316364633161356665666161356262303865303234326566316436
35326430336430313531656462383936633735346333353230656135623336613036373137326435
63303934386265306134346233336232633938343737313462643566623366633131383533633465
31636362363637646663316135616466346436646263393133383133646330396466373465613038
65386264646430303862336636636164356433356237663163373938336430326536313062343239
36326461323330633830303036663066373439613962626464303764386364366566303862383964
62323236336364636232323137613338663833363532306561343665343431656330303462633732
33336533316534353235336632393465323162313138346333626364663337323063376137303137
38643332373064393134336331396435326163643266656437393439386336336238623431643335
32363037383734356532626631303434356662353764643664306538393564346161323562333464
31323234373364313837663934646662366335663938333338313033333136323934643038366639
63386534303039616134613762616636356236373335383239333131636263363635376161313266
32356365393530343461623430323362363430663932383634646461343934366535303734346430
38376335373330663833626234653436366434626334333663383138653363373231353737356639
64636632373461316666663239633230346230646234313663333666636132323036323737633936
31643235316432623838343062633462366265343837353437633738656563613330333636306537
32366562336666356365393131363835363331336437366463336130323838326236353931396337
33336237663531633863396133396539623532383239326334626239353766313666613537343436
39616630383633393830353262313863333864633131303063636362376238623635643163316539
38656365363634353461313434316534613763396365303361363864356232306161306663346361
30316564326261303734636531386665653335386537333830306334313439663965623038366131
37323132623339353231663065373138653866393939323639393062323662303364356638616134
34613339303538663065666331336639376630386365613530656666313566343039656265643264
65636532646331313163333132353961393431353231316264333861633631363538313263616262
36376363613264646334383365393930613263343733336135613265633335326538643531326130
34323365343365613339343666376662376135343630303564616261353035393332386464393163
30373538613532316432336433306335396636656237666435656363306565663633653432356366
64343961316530353065303963373063666638353765363565646330633833323639393630393063
39303632333236653338303566353662373265353337356364363264633061643131316130376431
31306437373861336363373934373333303432623630393138663766653063613438613635333235
31363830383638613463386138383635316665646232633736636565396130646433383565323231
38363038386663613365383330633834633830663635313762326330643734383237623161383236
39646535376461396364396466353365383766323662363636333566653832396265303730653162
30633238366132343866373265353231623161653162313536626663323234623831316366366265
33633931353733653239623739636666623233373765333430383261393036323934643436393537
64396634636438663432653537653934313538313535303733643637623935663132646431323237
33653633306437303239383936303962363162353632633061613632393562316366313930623331
32303434653666646236303662316631366537396466373537316262303533363864393164326237
38646565323065623332353036316365623863646339363462616362633430633432663737633565
61633132636238393461623332633763653665326361363166613465383332303238313662326337
61613638316665343764653336373163396137363761626162306538396466313639303539323439
32303534333731366666383261636566646435663236646137656361333030316132626539396234
66376438336264306533303530633763623337353331333038323662396439323633343366363163
32656131623635356234616365376366303863663032653363366330346632356534633965623931
62366539303938386466623462396636396136616332323337393761656161363836646433626165
62656139333366623062633061656433333564313136303332356238363731313463633434636363
37623962633163396434643133663931653338613830613337386131396461346336376133653462
64383038363165326661386562633263626134386634323138323139363737646230353932643230
31626664383232323762616630316165663338313337363233633866383866366634303336313035
34386565316164373266326364353136316233623761346232386530336437303034343934646437
31623766633861666364393835323931393266623161306464373566373533333465366131653835
39343438336130623637343162383337383737353938643763393938313534643631643839366439
35613231663236346238346437303935623065656330356162653238343031633831326661623333
64653561663334333138343264393932653535616431666236303231323734376436303134633665
63623561623832303862306637383931626136323265633161316531646562303835363965323934
30623762656437313264653063316237333162663038666438356661613331656239393236656661
63633034353831656665353434303737333366396464353432613263366432643435626134386132
61326662623164363362303262343433663361623731656532656364643064643633346132623831
33383335646530656633376566343532393461623637626266386661346163396532663966633337
62643064643663356265623661376334303162333663653865323530343663643234366239643736
64383965383330613366616438356539353231376338396162623830663830623931306232343030
30313537626438613934353431393937643036373861623539303238323839373230333365623436
63626166643836633338303865346563373166373163633464386134343131313262303937633863
32313733663562343561613463626463646636396635333531326238323463656234396437343262
34616564306663333734343837396363636162303831356334663037646165623262393838316534
63316635653636396133323638663734333835636236373636616563646531663962326266346263
36393937366133643864626661376461333936313132316136383536646666666561363232306438
63346164666338373931626131336435393663386636643036656438376438623636306663613935
33386439373430353033613130646535643734643736373062363230386434346661363162663932
63383030616639333736313762323562636633356461336330373264376239353831643134353731
37363436326337343637353363366264346533313963356635396332326262363639636566353161
64393266666564623131366561346464366566633533656431303261663234336133646564623636
66323239653839333634633365346365623262653034333437313835326136396464383466313030
38613139633562346434613238396162656232656530373735336437386266393265343635363861
61656132643734303539346262336665356639313034303562326633323234663934643632653739
34646230653764613039373634613361316538346338326166333238613831386165323936393333
31393266363063663735623563306264623731383538313861393731373630383931653237653434
65643138396532383539303232663565626266303630363966346166613961653835333531613731
32633833376566363563653362323364633065393835343566643364353230366230336133303963
61623064613832373031383264653434363465653061326535656663363137613739653762353136
35363438633535336262313130316139363039343630313637366266306135346465636264643865
31663665636439613832666533653065656462633165616263663230653238623637636330666539
65356565376661343933633065623039353464353735303765313966613961303266343032303731
66313635313031636134373935306632653362656164373966316434653530303937363837343861
34303464356634303336393836373838386232613531346433366430306337653034323831616664
66393938616361333966396265613036313433643937393263616333396262663161646661623431
34396562393435643938373335663435316438376331313365356562386666626132383939643064
38316230663465653735323966306530346633383162646562633761636363636366393031633762
63393663363265313938653764626238666639386161383033323535646262333839613633376635
61343864663864613462346464306466613230636338343332383930643662663638316332373337
61323766343931643261313965313631356139353232323535303134326432353733303665366661
31373738343132373937333631343062396563316230353734303837346364633739663061306566
65613361353161313032616637326139653339336432316339363465373764343235396236303937
66346639373735393362343935333932373133336665373139346663333331643636383438393431
35383762326531633039656664636636643964666265323032366238323932326437353432383464
63613230616635383739333138393130663335303032366238343562666538313333653765353233
37656231613034306236303834343033383961653865366663313338333531323737343634623032
35333535356263346334343538333631373264323735386463646638333631303235636630353362
39656566343063343638316336396562363536353231663465303162343731336166336132353936
38636433373663336266653934633861646635323636663966643537323537376238646139343339
61323062346333396634353066373032663863643531333233643466303563363833613366623237
63343333336330306265383764363463613663353139376366303338356131643261353439626561
34323562663432383839663630626630346262353562363735356265383238396232303332323631
31316266383162356161336637326431373935333131323436323032383634346261313339326137
31373937336564643765656432303163633735376534316565633235646638623737366135646336
33396131323530336563336163643830613832303038313330353330306366333530613230386636
64393434383630323334633739313031653933326637313265323431646633366566333233646366
66626232633830323033343732326638663165653731656161313461666461356363363635306231
33666461626161343664383066376537376464643235633962313932316533303762636464323762
66373531363033306131396234646465343137346133353230313637613933623037646539373633
35343637313632323761376136373534333566646162633136313136646536353763316464373963
33646136646535366538366563646166633836313066653433323834633864363165396132643462
31373936333661366532326532633635613230303137663232653837303563306139353437353762
63323734363461306135633337346533386132393835626531326331333762623438616637323731
65666466376238396433613734363735313538656662376239316634323731376131656138313731
65383165393339313037306264643863393735623732636534323435326236613161353061313536
38613564353137313665333636353661636433353438313535326464363264613865633766313438
38393635613761396538316266653162313064363462373136643737383937346663373866393730
31313938323864343161366236383562306233303634396533333364323530353564636137316530
34343838663264396638326637373339653561333932333766323939363732613038646665653963
31303739623963313239383734303461613166303264323230663436613138303431646335306133
32313337656365643935666565353165303365663535363237663439353132643964643362306562
61326631623035643233346136663964333634346262333437363435383965323337356562653666
32336366613333373662336362616537613938666532643930393764336335313237666164366535
34343735653862356435373666333639646238396439646338346232626634653135393866366239
37636466306334396438653364313033663064353764396532316436336634633135626230383563
36396531326231386331303062656639356563646630643535333934343962626262616664323063
65373136626335366164326531653135383330663362313434363962346561346537646430666563
34303630613362383139366464646663366234323166376463346330396230646465373361616464
32663366653161306635643432383162333133373534643963383131646664376365643731313661
32353031366366633039336665363632373234356232623936313863623363633638663262323334
30663361653531346666326335393830363937346338613664663532643735386237343031666430
39393233656431353930326335323864363863343439643832663337653465313135323439326431
36613830306432623165383566666561646236353363393064346262633231643433386461323830
35396435316439363566633566396133363037646463313630303762643737663762636566656636
35616336663865376239623633376439336333366364623538363633313531666663346663303436
64633437303562303332336335366561323834356337336566333932373436666633653265346335
35383462333133353066613163636439623662613034653637613033383637383862373030643933
64333465643737316664633737653930363063666237643333353862353861356433633863383931
64326236333139613538666133353937323131646131656664303161366466383135656334303165
39303334636433653331613063353832306538613838346463323165383961306430646535303130
63363030646630396230653535653132363061396135363064323833636439626538326635353332
39363265313236316136393435643235306634646137636338376263373337333266616631346130
33396363306630303433383165613238623836666635646138363230343133353932353135393135
63336432666435333035303864346238343066383161633935363762376537376164343338343564
65323263326634313939383936383032323136643134353066623433386638393461363834616366
66373339313063653166336162366131316232386366383936393139353039373732646466646334
34353861643338383731303335363535386637343865633934353737346434643430376263366535
65313534353764363164626630383965336335323139323737653532303462323164303934653435
30313961393735386539303133656466353133396239353339376261336130386263386435303034
38333837356466393061353139333033333766653637343463376634383966616466346263383637
36323435313164623835383030316265643239346361646361316235306366373138623364643164
66616238363365366661633238643364613137303766353738396639346634313961653561653234
36323435306462363865336162636438336437386130633233366665386466333234376632393362
31643337383364626330643462663962303039316562316166333238376565326137356534376339
30346165646237616431623664626461636339303535623031333861383532336161613133386565
61643833336133363833623337653236613036376437656230623361333332646636316235376236
36393935343538326366383130653964346533623266616139326665333131663239323832306532
35336230326662666430653734613734313037393038626337613763316431323966393838623338
32383930376239623337373161613865306134343066353336633330353237636261353664633361
63653034356238316231623365313232393637656563323031386163656639323639643737356332
32613132373661373166323133613465633637336239626435643664316134626361653638373531
33663931363666336437316133366634333864653530333331343264643561613939616136646337
38646430633333656461363333333561616532643731346432396132663665633163353534323366
65376331333131333734313966386263376639663063653432613635346330363736646531616137
66656364373839626261333837393639623362393964333665636165383039623963356530356139
39626436356534633735616166386464626133303538323934393261313966333463643032396162
30306166333765616164346362623735313733613431333133386530616332353965323032656230
62313430363635636336393963306135383439656262366535333364376361633839613938326661
30383237653032326632656133323334386335636139363437366439613439643339313531336431
65396239303835636162643865373861636134356334616464633639663937346263303139333062
34333936363936346365643236643661623134633866393464646338303262663236663733343932
63646636333465373765626365353231303463613633336631656365333964353730633164383263
30643663363139353966626632393564663837653535343736616135613333656662306664663562
64353630393832663031356433393462346466353437356538356131303735323666613238326639
38313438643130636165613933323866356165313739623561336338386562613964653637333130
63636234313832373634346237366164356631396237303766663136343161663663343731633936
64363730363462336633616463333336396234613735303362343731313433326632643833343031
37363633623237383761613336393063646564633863343865353763396537316636356462626161
3237633462303530343434363865633963633461633866333238

View File

@@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@@ -1,53 +0,0 @@
"""Initial migration with users table
Revision ID: 001_initial
Revises:
Create Date: 2024-01-01 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '001_initial'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# Создание таблицы users
op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('hashed_password', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='true'),
sa.Column('is_superuser', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
# Создание пользователя admin/admin по умолчанию
# Пароль будет хеширован при первом входе через UserService
# Используем простой временный хеш, который будет обновлен при первом входе
import bcrypt
temp_hash = bcrypt.hashpw(b'admin', bcrypt.gensalt()).decode('utf-8')
# Используем параметризованный запрос
op.execute(
sa.text("""
INSERT INTO users (username, hashed_password, is_active, is_superuser)
VALUES ('admin', :hash, true, true)
ON CONFLICT (username) DO NOTHING;
""").bindparams(hash=temp_hash)
)
def downgrade() -> None:
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_table('users')

View File

@@ -1,119 +0,0 @@
"""Add playbooks and dockerfiles tables
Revision ID: 002_add_playbooks
Revises: 001_initial
Create Date: 2024-01-02 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '002_add_playbooks'
down_revision = '001_initial'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Создание таблицы playbooks
op.create_table(
'playbooks',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('roles', postgresql.JSON(astext_type=sa.Text()), nullable=False),
sa.Column('variables', postgresql.JSON(astext_type=sa.Text())),
sa.Column('inventory', sa.Text()),
sa.Column('status', sa.String(), server_default='active'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('created_by', sa.String()),
sa.Column('updated_by', sa.String()),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text())),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_playbooks_id'), 'playbooks', ['id'], unique=False)
op.create_index(op.f('ix_playbooks_name'), 'playbooks', ['name'], unique=True)
# Создание таблицы playbook_test_runs
op.create_table(
'playbook_test_runs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('playbook_id', sa.Integer(), nullable=False),
sa.Column('preset_name', sa.String()),
sa.Column('status', sa.String(), nullable=False),
sa.Column('started_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('finished_at', sa.DateTime()),
sa.Column('duration', sa.Integer()),
sa.Column('output', sa.Text()),
sa.Column('error', sa.Text()),
sa.Column('returncode', sa.Integer()),
sa.Column('user', sa.String()),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text())),
sa.ForeignKeyConstraint(['playbook_id'], ['playbooks.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_playbook_test_runs_id'), 'playbook_test_runs', ['id'], unique=False)
op.create_index(op.f('ix_playbook_test_runs_playbook_id'), 'playbook_test_runs', ['playbook_id'], unique=False)
op.create_index(op.f('ix_playbook_test_runs_preset_name'), 'playbook_test_runs', ['preset_name'], unique=False)
# Создание таблицы playbook_deployments
op.create_table(
'playbook_deployments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('playbook_id', sa.Integer(), nullable=False),
sa.Column('inventory', sa.Text()),
sa.Column('hosts', postgresql.JSON(astext_type=sa.Text())),
sa.Column('status', sa.String(), nullable=False),
sa.Column('started_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('finished_at', sa.DateTime()),
sa.Column('duration', sa.Integer()),
sa.Column('output', sa.Text()),
sa.Column('error', sa.Text()),
sa.Column('returncode', sa.Integer()),
sa.Column('user', sa.String()),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text())),
sa.ForeignKeyConstraint(['playbook_id'], ['playbooks.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_playbook_deployments_id'), 'playbook_deployments', ['id'], unique=False)
op.create_index(op.f('ix_playbook_deployments_playbook_id'), 'playbook_deployments', ['playbook_id'], unique=False)
# Создание таблицы dockerfiles
op.create_table(
'dockerfiles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('base_image', sa.String()),
sa.Column('tags', postgresql.JSON(astext_type=sa.Text())),
sa.Column('status', sa.String(), server_default='active'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('now()')),
sa.Column('created_by', sa.String()),
sa.Column('updated_by', sa.String()),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text())),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_dockerfiles_id'), 'dockerfiles', ['id'], unique=False)
op.create_index(op.f('ix_dockerfiles_name'), 'dockerfiles', ['name'], unique=True)
def downgrade() -> None:
op.drop_index(op.f('ix_dockerfiles_name'), table_name='dockerfiles')
op.drop_index(op.f('ix_dockerfiles_id'), table_name='dockerfiles')
op.drop_table('dockerfiles')
op.drop_index(op.f('ix_playbook_deployments_playbook_id'), table_name='playbook_deployments')
op.drop_index(op.f('ix_playbook_deployments_id'), table_name='playbook_deployments')
op.drop_table('playbook_deployments')
op.drop_index(op.f('ix_playbook_test_runs_preset_name'), table_name='playbook_test_runs')
op.drop_index(op.f('ix_playbook_test_runs_playbook_id'), table_name='playbook_test_runs')
op.drop_index(op.f('ix_playbook_test_runs_id'), table_name='playbook_test_runs')
op.drop_table('playbook_test_runs')
op.drop_index(op.f('ix_playbooks_name'), table_name='playbooks')
op.drop_index(op.f('ix_playbooks_id'), table_name='playbooks')
op.drop_table('playbooks')

View File

@@ -1,132 +0,0 @@
"""add presets table
Revision ID: 003
Revises: 002
Create Date: 2024-01-01 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '003'
down_revision = '002_add_playbooks'
branch_labels = None
depends_on = None
def upgrade():
# Создание таблицы presets
op.create_table(
'presets',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('category', sa.String(), nullable=True, server_default='main'),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('docker_network', sa.String(), nullable=True),
sa.Column('hosts', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('images', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('systemd_defaults', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('kind_clusters', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('status', sa.String(), nullable=True, server_default='active'),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('created_by', sa.String(), nullable=True),
sa.Column('updated_by', sa.String(), nullable=True),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_presets_id'), 'presets', ['id'], unique=False)
op.create_index(op.f('ix_presets_name'), 'presets', ['name'], unique=True)
op.create_index(op.f('ix_presets_category'), 'presets', ['category'], unique=False)
# Основные 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
# Подсчет хостов
hosts_count = len(preset_data.get('hosts', []))
connection.execute(
sa.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, :hosts, :images, :systemd_defaults, :kind_clusters, :created_at, :updated_at)
ON CONFLICT (name) DO NOTHING
"""),
{
'name': preset_file.stem,
'category': 'main',
'description': description,
'content': content,
'docker_network': preset_data.get('docker_network'),
'hosts': preset_data.get('hosts', []),
'images': preset_data.get('images', {}),
'systemd_defaults': preset_data.get('systemd_defaults', {}),
'kind_clusters': preset_data.get('kind_clusters', []),
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
except Exception as e:
print(f"Ошибка при импорте preset {preset_file.name}: {e}")
# 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
connection.execute(
sa.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, :hosts, :images, :systemd_defaults, :kind_clusters, :created_at, :updated_at)
ON CONFLICT (name) DO NOTHING
"""),
{
'name': preset_file.stem,
'category': 'k8s',
'description': description,
'content': content,
'docker_network': preset_data.get('docker_network'),
'hosts': preset_data.get('hosts', []),
'images': preset_data.get('images', {}),
'systemd_defaults': preset_data.get('systemd_defaults', {}),
'kind_clusters': preset_data.get('kind_clusters', []),
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
except Exception as e:
print(f"Ошибка при импорте k8s preset {preset_file.name}: {e}")
def downgrade():
op.drop_index(op.f('ix_presets_category'), table_name='presets')
op.drop_index(op.f('ix_presets_name'), table_name='presets')
op.drop_index(op.f('ix_presets_id'), table_name='presets')
op.drop_table('presets')

View File

@@ -1,233 +0,0 @@
"""Migrate presets and dockerfiles from filesystem to database
Revision ID: 004
Revises: 003
Create Date: 2024-01-03 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
import yaml
import json
from pathlib import Path
from datetime import datetime
# revision identifiers, used by Alembic.
revision = '004'
down_revision = '003'
branch_labels = None
depends_on = None
def upgrade():
"""Перенос preset'ов и dockerfiles из файловой системы в БД"""
connection = op.get_bind()
# Получаем путь к папке presets относительно файла миграции
# Пресеты теперь находятся в alembic/presets
alembic_dir = Path(__file__).parent.parent
presets_dir = alembic_dir / "presets"
k8s_presets_dir = presets_dir / "k8s"
# Если не найдено в alembic, пробуем старый путь (для обратной совместимости)
if not presets_dir.exists():
import os
project_root = Path(os.getenv("PROJECT_ROOT", "/workspace"))
old_presets_dir = project_root / "molecule" / "presets"
if old_presets_dir.exists():
presets_dir = old_presets_dir
k8s_presets_dir = presets_dir / "k8s"
# Функция для импорта preset'а
def import_preset(preset_file, category='main'):
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(
sa.text("SELECT id FROM presets WHERE name = :name"),
{"name": preset_file.stem}
)
if result.fetchone():
return False
# Преобразуем 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(
sa.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': category,
'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()
}
)
return True
except Exception as e:
print(f"Ошибка при импорте preset {preset_file.name}: {e}")
return False
# Основные preset'ы из корня папки presets
if presets_dir.exists():
for preset_file in presets_dir.glob("*.yml"):
if preset_file.name == "deploy.yml":
continue
import_preset(preset_file, category='main')
# Пресеты из папки examples
examples_dir = presets_dir / "examples"
if examples_dir.exists():
for preset_file in examples_dir.glob("*.yml"):
import_preset(preset_file, category='main')
# 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(
sa.text("SELECT id FROM presets WHERE name = :name"),
{"name": preset_file.stem}
)
if result.fetchone():
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(
sa.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()
}
)
except Exception as e:
print(f"Ошибка при импорте k8s preset {preset_file.name}: {e}")
# ========== ПЕРЕНОС DOCKERFILES ==========
# Dockerfiles теперь находятся в alembic/dockerfiles
# Используем путь относительно файла миграции
alembic_dir = Path(__file__).parent.parent
dockerfiles_dir = alembic_dir / "dockerfiles"
# Если не найдено, пробуем альтернативные пути
if not dockerfiles_dir.exists():
alt_paths = [
project_root / "app" / "alembic" / "dockerfiles",
Path("/app/app/alembic/dockerfiles"),
]
for alt_path in alt_paths:
if alt_path.exists():
dockerfiles_dir = alt_path
break
if dockerfiles_dir.exists():
for dockerfile_path in dockerfiles_dir.rglob("Dockerfile*"):
if not dockerfile_path.is_file():
continue
try:
# Имя из пути (например, ubuntu22/Dockerfile -> ubuntu22)
relative_path = dockerfile_path.relative_to(dockerfiles_dir)
name = str(relative_path.parent) if relative_path.parent != Path('.') else relative_path.stem
# Пропускаем специальные файлы
if name in ['ansible-controller', 'k8s', 'k8s-portforward']:
continue
# Проверка существования в БД
result = connection.execute(
sa.text("SELECT id FROM dockerfiles WHERE name = :name"),
{"name": name}
)
if result.fetchone():
continue
content = dockerfile_path.read_text(encoding='utf-8')
# Определяем базовый образ из содержимого
base_image = None
for line in content.split('\n'):
if line.strip().startswith('FROM'):
base_image = line.strip().replace('FROM', '').strip().split()[0]
break
# Платформы по умолчанию: linux/amd64 (x86_64), linux/386 (x86) и linux/arm64 (macOS M1)
default_platforms = ["linux/amd64", "linux/386", "linux/arm64"]
connection.execute(
sa.text("""
INSERT INTO dockerfiles (name, description, content, base_image, tags, platforms, status, created_at, updated_at)
VALUES (:name, :description, :content, :base_image, :tags, :platforms, :status, :created_at, :updated_at)
"""),
{
'name': name,
'description': f'Dockerfile for {name}',
'content': content,
'base_image': base_image,
'tags': None,
'platforms': default_platforms,
'status': 'active',
'created_at': datetime.utcnow(),
'updated_at': datetime.utcnow()
}
)
except Exception as e:
print(f"Ошибка при импорте dockerfile {dockerfile_path}: {e}")
def downgrade():
# При откате миграции данные остаются в БД
# Удаление файлов не выполняется для безопасности
pass

View File

@@ -1,47 +0,0 @@
"""Add user profiles table
Revision ID: 005
Revises: 004
Create Date: 2024-01-04 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '005'
down_revision = '004'
branch_labels = None
depends_on = None
def upgrade():
# Создание таблицы user_profiles
op.create_table(
'user_profiles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('dockerhub_username', sa.String(), nullable=True),
sa.Column('dockerhub_password', sa.Text(), nullable=True),
sa.Column('dockerhub_repository', sa.String(), nullable=True),
sa.Column('harbor_url', sa.String(), nullable=True),
sa.Column('harbor_username', sa.String(), nullable=True),
sa.Column('harbor_password', sa.Text(), nullable=True),
sa.Column('harbor_project', sa.String(), nullable=True),
sa.Column('email', sa.String(), nullable=True),
sa.Column('full_name', sa.String(), nullable=True),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_profiles_id'), 'user_profiles', ['id'], unique=False)
op.create_index(op.f('ix_user_profiles_user_id'), 'user_profiles', ['user_id'], unique=True)
def downgrade():
op.drop_index(op.f('ix_user_profiles_user_id'), table_name='user_profiles')
op.drop_index(op.f('ix_user_profiles_id'), table_name='user_profiles')
op.drop_table('user_profiles')

View File

@@ -1,46 +0,0 @@
"""Add platforms column to dockerfiles table
Revision ID: 006
Revises: 005
Create Date: 2024-01-04 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
import json
# revision identifiers, used by Alembic.
revision = '006'
down_revision = '005'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Добавление поля platforms в таблицу dockerfiles"""
connection = op.get_bind()
# Добавляем колонку platforms (JSON) с дефолтным значением
op.add_column(
'dockerfiles',
sa.Column('platforms', postgresql.JSON(astext_type=sa.Text()), nullable=True)
)
# Устанавливаем дефолтные значения для существующих записей
# По умолчанию: linux/amd64 (x86_64), linux/386 (x86) и linux/arm64 (macOS M1)
default_platforms = ["linux/amd64", "linux/386", "linux/arm64"]
# Используем CAST для преобразования строки JSON в JSONB
connection.execute(
sa.text("""
UPDATE dockerfiles
SET platforms = CAST(:platforms AS jsonb)
WHERE platforms IS NULL
"""),
{"platforms": json.dumps(default_platforms)}
)
def downgrade() -> None:
"""Удаление поля platforms из таблицы dockerfiles"""
op.drop_column('dockerfiles', 'platforms')

View File

@@ -1,49 +0,0 @@
"""Add dockerfile_build_logs table
Revision ID: 007
Revises: 006
Create Date: 2024-01-05 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '007'
down_revision = '006'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Создание таблицы dockerfile_build_logs для хранения логов сборки"""
op.create_table(
'dockerfile_build_logs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('dockerfile_id', sa.Integer(), nullable=False),
sa.Column('image_name', sa.String(), nullable=False),
sa.Column('tag', sa.String(), server_default='latest', nullable=True),
sa.Column('platforms', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('status', sa.String(), nullable=False, server_default='running'),
sa.Column('logs', sa.Text(), nullable=True),
sa.Column('started_at', sa.DateTime(), nullable=False),
sa.Column('finished_at', sa.DateTime(), nullable=True),
sa.Column('duration', sa.Integer(), nullable=True),
sa.Column('returncode', sa.Integer(), nullable=True),
sa.Column('user', sa.String(), nullable=True),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.ForeignKeyConstraint(['dockerfile_id'], ['dockerfiles.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_dockerfile_build_logs_dockerfile_id'), 'dockerfile_build_logs', ['dockerfile_id'], unique=False)
op.create_index(op.f('ix_dockerfile_build_logs_image_name'), 'dockerfile_build_logs', ['image_name'], unique=False)
op.create_index(op.f('ix_dockerfile_build_logs_started_at'), 'dockerfile_build_logs', ['started_at'], unique=False)
def downgrade() -> None:
"""Удаление таблицы dockerfile_build_logs"""
op.drop_index(op.f('ix_dockerfile_build_logs_started_at'), table_name='dockerfile_build_logs')
op.drop_index(op.f('ix_dockerfile_build_logs_image_name'), table_name='dockerfile_build_logs')
op.drop_index(op.f('ix_dockerfile_build_logs_dockerfile_id'), table_name='dockerfile_build_logs')
op.drop_table('dockerfile_build_logs')

View File

@@ -1,58 +0,0 @@
"""Add roles table
Revision ID: 008
Revises: 007
Create Date: 2024-01-06 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '008'
down_revision = '007'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Создание таблицы roles для хранения Ansible ролей"""
op.create_table(
'roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('content', postgresql.JSON(astext_type=sa.Text()), nullable=False),
sa.Column('is_global', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('is_personal', sa.Boolean(), nullable=False, server_default='false'),
sa.Column('groups', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('author', sa.String(), nullable=True),
sa.Column('platforms', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('galaxy_info', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('status', sa.String(), server_default='active', nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('created_by', sa.String(), nullable=True),
sa.Column('updated_by', sa.String(), nullable=True),
sa.Column('extra_data', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=True)
op.create_index(op.f('ix_roles_is_global'), 'roles', ['is_global'], unique=False)
op.create_index(op.f('ix_roles_is_personal'), 'roles', ['is_personal'], unique=False)
op.create_index(op.f('ix_roles_user_id'), 'roles', ['user_id'], unique=False)
op.create_index(op.f('ix_roles_created_at'), 'roles', ['created_at'], unique=False)
def downgrade() -> None:
"""Удаление таблицы roles"""
op.drop_index(op.f('ix_roles_created_at'), table_name='roles')
op.drop_index(op.f('ix_roles_user_id'), table_name='roles')
op.drop_index(op.f('ix_roles_is_personal'), table_name='roles')
op.drop_index(op.f('ix_roles_is_global'), table_name='roles')
op.drop_index(op.f('ix_roles_name'), table_name='roles')
op.drop_table('roles')

View File

@@ -1,200 +0,0 @@
"""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/ остаются для безопасности.")

View File

@@ -1 +0,0 @@
# API package

View File

@@ -1 +0,0 @@
# API v1 package

View File

@@ -1 +0,0 @@
# Endpoints package

View File

@@ -1,184 +0,0 @@
"""
API endpoints для аутентификации
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Depends, status, Form
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi.security import OAuth2PasswordRequestForm
from pathlib import Path
from datetime import timedelta
from app.core.config import settings
from app.auth.security import create_access_token
from app.auth.deps import get_current_user
from app.db.session import get_async_db
from app.services.user_service import UserService
from sqlalchemy.ext.asyncio import AsyncSession
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
"""Страница входа"""
return templates.TemplateResponse(
"pages/auth/login.html",
{"request": request}
)
@router.post("/api/v1/auth/login")
async def login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(get_async_db)
):
"""API endpoint для входа"""
# Убеждаемся, что пользователь admin существует
await UserService.ensure_admin_user(db)
# Получаем пользователя из БД
user = await UserService.get_user_by_username(db, form_data.username)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверное имя пользователя или пароль",
headers={"WWW-Authenticate": "Bearer"},
)
# Проверяем пароль
if not await UserService.verify_user_password(user, form_data.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверное имя пользователя или пароль",
headers={"WWW-Authenticate": "Bearer"},
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Пользователь отключен"
)
# Если пароль был "admin" и не хеширован, обновляем его
if user.hashed_password == "admin" or len(user.hashed_password) < 50:
await UserService.update_password(db, user, form_data.password)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires
)
response = JSONResponse(content={
"access_token": access_token,
"token_type": "bearer"
})
# Устанавливаем токен в cookie
response.set_cookie(
key="access_token",
value=access_token,
max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
httponly=True,
secure=False, # В продакшене должно быть True для HTTPS
samesite="lax"
)
return response
@router.get("/logout")
@router.head("/logout") # Поддержка HEAD запросов
async def logout():
"""Выход (удаляет токен из cookie и перенаправляет на страницу входа)
Доступен по путям:
- /logout (через прямой router в main.py)
- /api/v1/auth/logout (через api_router с префиксом /auth)
"""
from fastapi.responses import RedirectResponse
response = RedirectResponse(url="/login", status_code=302)
response.delete_cookie(
key="access_token",
httponly=True,
secure=False,
samesite="lax"
)
return response
@router.get("/api/v1/auth/me")
async def get_current_user_info(
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Получение информации о текущем пользователе"""
username = current_user.get("username")
if username:
user = await UserService.get_user_by_username(db, username)
if user:
return {
"username": user.username,
"is_active": user.is_active,
"is_superuser": user.is_superuser,
"created_at": user.created_at.isoformat() if user.created_at else None
}
return current_user
@router.get("/change-password", response_class=HTMLResponse)
async def change_password_page(request: Request, current_user: dict = Depends(get_current_user)):
"""Страница смены пароля"""
return templates.TemplateResponse(
"pages/auth/change-password.html",
{
"request": request,
"current_user": current_user
}
)
@router.post("/api/v1/auth/change-password")
async def change_password(
current_password: str = Form(...),
new_password: str = Form(...),
new_password_confirm: str = Form(...),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Смена пароля пользователя"""
# Проверка совпадения паролей
if new_password != new_password_confirm:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Новые пароли не совпадают"
)
username = current_user.get("username")
if not username:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Пользователь не авторизован"
)
user = await UserService.get_user_by_username(db, username)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Пользователь не найден"
)
# Проверяем текущий пароль
if not await UserService.verify_user_password(user, current_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Неверный текущий пароль"
)
# Обновляем пароль
await UserService.update_password(db, user, new_password)
return {"message": "Пароль успешно изменен"}

View File

@@ -1,254 +0,0 @@
"""
API endpoints для деплоя на живые серверы
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Dict, Optional, List
import yaml
import json
from app.core.config import settings
from app.services.deployment_service import DeploymentService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
deployment_service = DeploymentService()
@router.get("/deploy", response_class=HTMLResponse)
async def deploy_page(request: Request):
"""Страница деплоя"""
# Проверка наличия inventory
inventory_file = settings.PROJECT_ROOT / "inventory" / "hosts.ini"
inventory_exists = inventory_file.exists()
# Чтение inventory если существует
inventory_content = ""
inventory_data = {}
if inventory_exists:
try:
inventory_content = inventory_file.read_text()
# Парсинг inventory для отображения групп и хостов
inventory_data = parse_inventory(inventory_content)
except Exception:
pass
# Получение списка ролей
roles_dir = settings.PROJECT_ROOT / "roles"
roles = []
if roles_dir.exists():
for role_dir in roles_dir.iterdir():
if role_dir.is_dir() and role_dir.name != "deploy.yml":
roles.append(role_dir.name)
return templates.TemplateResponse(
"pages/deploy/index.html",
{
"request": request,
"inventory_exists": inventory_exists,
"inventory_content": inventory_content,
"inventory_data": inventory_data,
"roles": sorted(roles)
}
)
@router.get("/deploy/inventory", response_class=HTMLResponse)
async def inventory_page(request: Request):
"""Страница управления inventory"""
inventory_file = settings.PROJECT_ROOT / "inventory" / "hosts.ini"
inventory_exists = inventory_file.exists()
inventory_content = ""
if inventory_exists:
inventory_content = inventory_file.read_text()
return templates.TemplateResponse(
"pages/deploy/inventory.html",
{
"request": request,
"inventory_exists": inventory_exists,
"inventory_content": inventory_content
}
)
@router.post("/api/v1/deploy/inventory")
async def save_inventory(request: Request):
"""Сохранение inventory файла"""
form_data = await request.form()
content = form_data.get("content", "")
if not content:
return JSONResponse(
status_code=400,
content={"success": False, "message": "Содержимое inventory не может быть пустым"}
)
inventory_dir = settings.PROJECT_ROOT / "inventory"
inventory_dir.mkdir(parents=True, exist_ok=True)
inventory_file = inventory_dir / "hosts.ini"
inventory_file.write_text(content)
return JSONResponse(
content={
"success": True,
"message": "Inventory файл успешно сохранен"
}
)
@router.post("/api/v1/deploy/start")
async def start_deploy(
role_name: Optional[str] = None,
tags: Optional[str] = None,
limit: Optional[str] = None,
check: bool = False,
extra_vars: Optional[str] = None
):
"""Запуск деплоя"""
# TODO: Запуск через Celery для фонового выполнения
# Пока просто возвращаем информацию
deploy_id = f"deploy-{role_name or 'all'}-{tags or 'none'}"
return {
"success": True,
"deploy_id": deploy_id,
"role_name": role_name,
"tags": tags,
"limit": limit,
"check": check,
"message": "Деплой запущен"
}
@router.websocket("/ws/deploy/{deploy_id}")
async def deploy_websocket(websocket: WebSocket, deploy_id: str):
"""WebSocket для live логов деплоя"""
await websocket.accept()
try:
# Ждем сообщение от клиента с параметрами деплоя
message = await websocket.receive_json()
if message.get("type") != "start":
await websocket.send_json({
"type": "error",
"data": "Ожидается сообщение типа 'start' с параметрами деплоя"
})
await websocket.close()
return
# Получаем параметры из сообщения
role_name = message.get("role_name")
inventory = message.get("inventory", "inventory/hosts.ini")
limit = message.get("limit")
tags = message.get("tags")
check = message.get("check", False)
extra_vars = message.get("extra_vars")
if not role_name:
await websocket.send_json({
"type": "error",
"data": "Не указано имя роли"
})
await websocket.close()
return
# Отправка начального сообщения
await websocket.send_json({
"type": "info",
"data": f"🚀 Запуск деплоя роли '{role_name}'..."
})
if check:
await websocket.send_json({
"type": "warning",
"data": "⚠️ Режим dry-run (--check) - изменения не будут применены"
})
# Запуск деплоя через DeploymentService
async for line in deployment_service.deploy_role(
role_name=role_name,
inventory=inventory,
limit=limit,
tags=tags,
check=check,
extra_vars=extra_vars,
stream=True
):
line = line.rstrip()
if not line:
continue
# Определение типа лога
log_type = deployment_service.detect_log_level(line)
await websocket.send_json({
"type": "log",
"level": log_type,
"data": line
})
# Завершение
await websocket.send_json({
"type": "complete",
"status": "success",
"data": "✅ Деплой завершен успешно"
})
except WebSocketDisconnect:
pass
except Exception as e:
import traceback
error_msg = f"❌ Ошибка: {str(e)}\n{traceback.format_exc()}"
await websocket.send_json({
"type": "error",
"data": error_msg
})
finally:
try:
await websocket.close()
except:
pass
def parse_inventory(content: str) -> Dict:
"""Парсинг inventory файла для отображения структуры"""
result = {
"groups": {},
"hosts": []
}
current_group = None
for line in content.split("\n"):
line = line.strip()
if not line or line.startswith("#"):
continue
# Группа
if line.startswith("[") and line.endswith("]"):
group_name = line[1:-1]
if ":" in group_name:
group_name = group_name.split(":")[0]
current_group = group_name
if current_group not in result["groups"]:
result["groups"][current_group] = []
# Хост
elif current_group and " " in line:
host_parts = line.split()
host_name = host_parts[0]
result["groups"][current_group].append(host_name)
result["hosts"].append({
"name": host_name,
"group": current_group,
"vars": " ".join(host_parts[1:]) if len(host_parts) > 1 else ""
})
return result

View File

@@ -1,116 +0,0 @@
"""
API endpoints для управления Docker образами
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Dict
from app.core.config import settings
from app.core.docker_client import DockerClient
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
def get_docker_client():
"""Получение Docker клиента с обработкой ошибок"""
try:
return DockerClient()
except Exception:
return None
@router.get("/docker", response_class=HTMLResponse)
async def docker_page(request: Request):
"""Страница управления Docker образами"""
docker_client = get_docker_client()
images = []
if docker_client:
try:
images = docker_client.list_images()
except Exception:
pass
return templates.TemplateResponse(
"pages/docker/index.html",
{
"request": request,
"images": images
}
)
@router.get("/api/v1/docker/images", response_class=HTMLResponse)
async def get_docker_images():
"""API endpoint для получения списка Docker образов"""
docker_client = get_docker_client()
if not docker_client:
return """
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Docker недоступен</strong>
<br><small class="text-muted">Убедитесь, что Docker запущен и доступен. Проверьте, что Docker socket доступен: <code>/var/run/docker.sock</code></small>
</div>
"""
try:
images = docker_client.list_images()
if not images:
return """
<div class="text-center py-5">
<i class="fab fa-docker fa-3x text-muted mb-3"></i>
<p class="text-muted">Docker образы не найдены</p>
</div>
"""
html = '<div class="table-responsive"><table class="table table-hover"><thead><tr><th>ID</th><th>Теги</th><th>Размер</th><th>Создан</th></tr></thead><tbody>'
for img in images:
size_mb = (img.get("size", 0) / 1024 / 1024) if img.get("size") else 0
size_str = f"{size_mb:.2f} MB" if size_mb > 0 else "N/A"
tags_list = img.get("tags", [])
if tags_list:
tags = " ".join([f'<span class="badge bg-info me-1">{tag}</span>' for tag in tags_list])
else:
tags = '<span class="text-muted">нет тегов</span>'
img_id = img.get("id", "")[:12] if img.get("id") else "N/A"
created = img.get("created", "")
if created:
try:
# Парсим ISO формат даты или timestamp
from datetime import datetime
if isinstance(created, (int, float)):
# Unix timestamp
dt = datetime.fromtimestamp(created)
created_str = dt.strftime('%d.%m.%Y %H:%M')
elif isinstance(created, str):
# ISO формат
try:
dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
except:
# Пробуем другой формат
dt = datetime.strptime(created.split('.')[0], '%Y-%m-%dT%H:%M:%S')
created_str = dt.strftime('%d.%m.%Y %H:%M')
else:
created_str = str(created)
except Exception as e:
created_str = str(created)
else:
created_str = "N/A"
html += f'<tr><td><code>{img_id}</code></td><td>{tags}</td><td>{size_str}</td><td>{created_str}</td></tr>'
html += '</tbody></table></div>'
return html
except Exception as e:
return f"""
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>Ошибка при получении списка образов:</strong> {str(e)}
</div>
"""

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +0,0 @@
"""
API endpoints для экспорта ролей
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Optional
import json
from app.core.config import settings
from app.services.export_service import ExportService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
export_service = ExportService()
@router.get("/roles/{role_name}/export", response_class=HTMLResponse)
async def export_role_page(request: Request, role_name: str):
"""Страница экспорта роли"""
# Проверка существования роли
roles_dir = settings.PROJECT_ROOT / "roles" / role_name
if not roles_dir.exists():
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
# Получение доступных компонентов
components = export_service.get_role_components(role_name)
return templates.TemplateResponse(
"pages/roles/export.html",
{
"request": request,
"role_name": role_name,
"components": components
}
)
@router.post("/api/v1/roles/{role_name}/export")
async def export_role_api(
role_name: str,
repo_url: str = Form(...),
branch: str = Form("main"),
version: Optional[str] = Form(None),
components: str = Form(""), # JSON строка
include_secrets: bool = Form(False),
commit_message: Optional[str] = Form(None)
):
"""API endpoint для экспорта роли"""
try:
# Парсинг компонентов
components_list = []
if components:
components_list = json.loads(components)
# Запуск экспорта через Celery (в будущем)
# Пока синхронно
result = await export_service.export_role(
role_name=role_name,
repo_url=repo_url,
branch=branch,
version=version,
components=components_list if components_list else None,
include_secrets=include_secrets,
commit_message=commit_message
)
return JSONResponse(content=result, status_code=200)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при экспорте роли: {str(e)}")
@router.get("/api/v1/roles/{role_name}/export/components")
async def get_role_components_api(role_name: str):
"""Получение списка компонентов роли"""
try:
components = export_service.get_role_components(role_name)
return {"components": components}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -1,82 +0,0 @@
"""
API endpoints для импорта ролей
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Optional
from app.core.config import settings
from app.services.import_service import ImportService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
import_service = ImportService()
@router.get("/roles/import", response_class=HTMLResponse)
async def import_role_page(request: Request):
"""Страница импорта роли"""
return templates.TemplateResponse(
"pages/roles/import.html",
{"request": request}
)
@router.post("/api/v1/roles/import/git")
async def import_from_git_api(
repo_url: str = Form(...),
role_name: Optional[str] = Form(None),
branch: str = Form("main"),
subdirectory: Optional[str] = Form(None)
):
"""API endpoint для импорта роли из Git репозитория"""
try:
result = await import_service.import_from_git(
repo_url=repo_url,
role_name=role_name,
branch=branch,
subdirectory=subdirectory
)
return JSONResponse(content=result, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при импорте роли: {str(e)}")
@router.post("/api/v1/roles/import/galaxy")
async def import_from_galaxy_api(
role_name: str = Form(...),
version: Optional[str] = Form(None),
namespace: Optional[str] = Form(None)
):
"""API endpoint для импорта роли из Ansible Galaxy"""
try:
result = await import_service.import_from_galaxy(
role_name=role_name,
version=version,
namespace=namespace
)
return JSONResponse(content=result, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при импорте роли: {str(e)}")
@router.post("/api/v1/roles/import/validate")
async def validate_repo_api(
repo_url: str = Form(...),
branch: str = Form("main")
):
"""Проверка доступности репозитория"""
try:
result = await import_service.validate_repo(repo_url, branch)
return JSONResponse(content=result)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -1,83 +0,0 @@
"""
API endpoints для импорта ролей
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Optional
from app.core.config import settings
from app.services.import_service import ImportService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
import_service = ImportService()
@router.get("/roles/import", response_class=HTMLResponse)
async def import_role_page(request: Request):
"""Страница импорта роли"""
# Этот роут должен быть зарегистрирован ПЕРЕД /roles/{role_name}
return templates.TemplateResponse(
"pages/roles/import.html",
{"request": request}
)
@router.post("/api/v1/roles/import/git")
async def import_from_git_api(
repo_url: str = Form(...),
role_name: Optional[str] = Form(None),
branch: str = Form("main"),
subdirectory: Optional[str] = Form(None)
):
"""API endpoint для импорта роли из Git репозитория"""
try:
result = await import_service.import_from_git(
repo_url=repo_url,
role_name=role_name,
branch=branch,
subdirectory=subdirectory
)
return JSONResponse(content=result, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при импорте роли: {str(e)}")
@router.post("/api/v1/roles/import/galaxy")
async def import_from_galaxy_api(
role_name: str = Form(...),
version: Optional[str] = Form(None),
namespace: Optional[str] = Form(None)
):
"""API endpoint для импорта роли из Ansible Galaxy"""
try:
result = await import_service.import_from_galaxy(
role_name=role_name,
version=version,
namespace=namespace
)
return JSONResponse(content=result, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при импорте роли: {str(e)}")
@router.post("/api/v1/roles/import/validate")
async def validate_repo_api(
repo_url: str = Form(...),
branch: str = Form("main")
):
"""Проверка доступности репозитория"""
try:
result = await import_service.validate_repo(repo_url, branch)
return JSONResponse(content=result)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -1,62 +0,0 @@
"""
API endpoints для управления Kubernetes
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Dict
from app.core.config import settings
from app.core.make_executor import MakeExecutor
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
executor = MakeExecutor()
@router.get("/k8s", response_class=HTMLResponse)
async def k8s_page(request: Request):
"""Страница управления Kubernetes"""
return templates.TemplateResponse(
"pages/k8s/index.html",
{"request": request}
)
@router.get("/api/v1/k8s/clusters", response_class=HTMLResponse)
async def get_k8s_clusters():
"""API endpoint для получения списка K8s кластеров"""
try:
# Попытка получить кластеры через kind
result = executor.execute("kind get clusters", capture_output=True)
if result.returncode == 0 and result.stdout.strip():
clusters = [c.strip() for c in result.stdout.strip().split('\n') if c.strip()]
if clusters:
html = '<div class="table-responsive"><table class="table table-hover"><thead><tr><th>Имя кластера</th><th>Действия</th></tr></thead><tbody>'
for cluster in clusters:
html += f'<tr><td><code>{cluster}</code></td><td><button class="btn btn-sm btn-outline-primary">Детали</button></td></tr>'
html += '</tbody></table></div>'
return html
# Если кластеров нет или kind не установлен
return """
<div class="text-center py-5">
<i class="fas fa-cube fa-3x text-muted mb-3"></i>
<p class="text-muted mb-2">Kubernetes кластеры не найдены</p>
<p class="text-muted small">
Используйте команду <code>make k8s create kubernetes</code> для создания кластера через Kind.
</p>
</div>
"""
except Exception as e:
return f"""
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Ошибка при получении списка кластеров:</strong> {str(e)}
<br><small class="text-muted">Убедитесь, что Kind установлен и доступен</small>
</div>
"""

View File

@@ -1,132 +0,0 @@
"""
API endpoints для проверки синтаксиса ролей (lint)
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Optional
from app.core.config import settings
from app.services.lint_service import LintService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
lint_service = LintService()
@router.get("/lint", response_class=HTMLResponse)
async def lint_page(request: Request):
"""Страница проверки синтаксиса ролей"""
# Получение списка ролей
roles_dir = settings.PROJECT_ROOT / "roles"
roles = []
if roles_dir.exists():
for role_dir in roles_dir.iterdir():
if role_dir.is_dir() and role_dir.name != "deploy.yml":
roles.append({
"name": role_dir.name,
"path": str(role_dir)
})
return templates.TemplateResponse(
"pages/lint/index.html",
{
"request": request,
"roles": sorted(roles, key=lambda x: x["name"])
}
)
@router.get("/roles/{role_name}/lint", response_class=HTMLResponse)
async def lint_role_page(request: Request, role_name: str):
"""Страница проверки синтаксиса конкретной роли"""
# Проверка существования роли
roles_dir = settings.PROJECT_ROOT / "roles" / role_name
if not roles_dir.exists():
from fastapi import HTTPException
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
return templates.TemplateResponse(
"pages/lint/role.html",
{
"request": request,
"role_name": role_name
}
)
@router.websocket("/ws/lint/{role_name}")
async def lint_websocket(websocket: WebSocket, role_name: str):
"""WebSocket для live логов линтинга"""
await websocket.accept()
try:
# Проверка существования роли (если указана)
if role_name != "all":
roles_dir = settings.PROJECT_ROOT / "roles" / role_name
if not roles_dir.exists():
await websocket.send_json({
"type": "error",
"data": f"Роль '{role_name}' не найдена"
})
await websocket.close()
return
# Отправка начального сообщения
if role_name == "all":
await websocket.send_json({
"type": "info",
"data": "🔍 Запуск проверки синтаксиса всех ролей..."
})
else:
await websocket.send_json({
"type": "info",
"data": f"🔍 Запуск проверки синтаксиса роли '{role_name}'..."
})
# Запуск линтинга
role_name_param = None if role_name == "all" else role_name
async for line in lint_service.lint_role(
role_name=role_name_param,
stream=True
):
# Очистка строки от лишних символов
line = line.rstrip()
if not line:
continue
# Определение типа лога
log_type = lint_service.detect_log_level(line)
await websocket.send_json({
"type": "log",
"level": log_type,
"data": line
})
# Завершение
await websocket.send_json({
"type": "complete",
"status": "success",
"data": "✅ Линтинг завершен"
})
except WebSocketDisconnect:
pass
except Exception as e:
import traceback
error_msg = f"❌ Ошибка: {str(e)}\n{traceback.format_exc()}"
await websocket.send_json({
"type": "error",
"data": error_msg
})
finally:
try:
await websocket.close()
except:
pass

View File

@@ -1,452 +0,0 @@
"""
API endpoints для управления playbook
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Depends, status, Form, WebSocket
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Optional, Dict
from pydantic import BaseModel
from app.db.session import get_async_db
from app.services.playbook_service import PlaybookService
from app.auth.deps import get_current_user
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.database import PlaybookTestRun, PlaybookDeployment
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
class PlaybookCreate(BaseModel):
name: str
description: Optional[str] = None
roles: List[str]
variables: Optional[Dict] = None
inventory: Optional[str] = None
class PlaybookUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
roles: Optional[List[str]] = None
variables: Optional[Dict] = None
inventory: Optional[str] = None
content: Optional[str] = None
@router.get("/playbooks", response_class=HTMLResponse)
async def playbooks_list(
request: Request,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Список всех playbook"""
playbooks = await PlaybookService.list_playbooks(db)
return templates.TemplateResponse(
"pages/playbooks/list.html",
{
"request": request,
"playbooks": playbooks
}
)
@router.get("/playbooks/create", response_class=HTMLResponse)
async def playbook_create_page(
request: Request,
current_user: dict = Depends(get_current_user)
):
"""Страница создания playbook"""
from app.core.config import settings
# Получаем список доступных ролей
roles_dir = settings.PROJECT_ROOT / "roles"
roles = []
if roles_dir.exists():
roles = [d.name for d in roles_dir.iterdir() if d.is_dir() and (d / "tasks").exists()]
return templates.TemplateResponse(
"pages/playbooks/create.html",
{
"request": request,
"roles": sorted(roles)
}
)
@router.get("/playbooks/{playbook_id}", response_class=HTMLResponse)
async def playbook_detail(
request: Request,
playbook_id: int,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Детали playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
raise HTTPException(status_code=404, detail="Playbook не найден")
# Получаем историю тестов и деплоев
test_runs = await db.execute(
select(PlaybookTestRun)
.where(PlaybookTestRun.playbook_id == playbook_id)
.order_by(PlaybookTestRun.started_at.desc())
.limit(10)
)
deployments = await db.execute(
select(PlaybookDeployment)
.where(PlaybookDeployment.playbook_id == playbook_id)
.order_by(PlaybookDeployment.started_at.desc())
.limit(10)
)
return templates.TemplateResponse(
"pages/playbooks/detail.html",
{
"request": request,
"playbook": playbook,
"test_runs": test_runs.scalars().all(),
"deployments": deployments.scalars().all()
}
)
@router.get("/playbooks/{playbook_id}/edit", response_class=HTMLResponse)
async def playbook_edit_page(
request: Request,
playbook_id: int,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Страница редактирования playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
raise HTTPException(status_code=404, detail="Playbook не найден")
from app.core.config import settings
# Получаем список доступных ролей
roles_dir = settings.PROJECT_ROOT / "roles"
all_roles = []
if roles_dir.exists():
all_roles = [d.name for d in roles_dir.iterdir() if d.is_dir() and (d / "tasks").exists()]
return templates.TemplateResponse(
"pages/playbooks/edit.html",
{
"request": request,
"playbook": playbook,
"all_roles": sorted(all_roles)
}
)
@router.post("/api/v1/playbooks")
async def create_playbook(
playbook: PlaybookCreate,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Создание нового playbook"""
# Проверяем, что playbook с таким именем не существует
existing = await PlaybookService.get_playbook_by_name(db, playbook.name)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Playbook с именем '{playbook.name}' уже существует"
)
new_playbook = await PlaybookService.create_playbook(
db=db,
name=playbook.name,
roles=playbook.roles,
description=playbook.description,
variables=playbook.variables,
inventory=playbook.inventory,
created_by=current_user.get("username")
)
return {"id": new_playbook.id, "name": new_playbook.name, "message": "Playbook создан успешно"}
@router.put("/api/v1/playbooks/{playbook_id}")
async def update_playbook(
playbook_id: int,
playbook: PlaybookUpdate,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Обновление playbook"""
updated = await PlaybookService.update_playbook(
db=db,
playbook_id=playbook_id,
name=playbook.name,
description=playbook.description,
roles=playbook.roles,
variables=playbook.variables,
inventory=playbook.inventory,
content=playbook.content,
updated_by=current_user.get("username")
)
if not updated:
raise HTTPException(status_code=404, detail="Playbook не найден")
return {"message": "Playbook обновлен успешно"}
@router.delete("/api/v1/playbooks/{playbook_id}")
async def delete_playbook(
playbook_id: int,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Удаление playbook"""
# Получаем имя playbook до удаления
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
raise HTTPException(status_code=404, detail="Playbook не найден")
playbook_name = playbook.name
# Удаляем playbook
deleted = await PlaybookService.delete_playbook(db, playbook_id)
if not deleted:
raise HTTPException(status_code=404, detail="Playbook не найден")
return JSONResponse(content={
"success": True,
"playbook_id": playbook_id,
"playbook_name": playbook_name,
"message": f"Playbook '{playbook_name}' успешно удален"
})
@router.get("/api/v1/playbooks")
async def list_playbooks_api(
status_filter: Optional[str] = None,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""API: Список всех playbook"""
playbooks = await PlaybookService.list_playbooks(db, status=status_filter)
return [
{
"id": p.id,
"name": p.name,
"description": p.description,
"roles": p.roles,
"status": p.status,
"created_at": p.created_at.isoformat() if p.created_at else None,
"updated_at": p.updated_at.isoformat() if p.updated_at else None
}
for p in playbooks
]
@router.post("/api/v1/playbooks/{playbook_id}/test")
async def test_playbook(
playbook_id: int,
preset: Optional[str] = Form("default"),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Запуск тестирования playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
raise HTTPException(status_code=404, detail="Playbook не найден")
# Сохраняем запись о тесте
test_run = await PlaybookService.save_test_run(
db=db,
playbook_id=playbook_id,
preset_name=preset,
status="running",
user=current_user.get("username")
)
# Запускаем тест в фоне через Celery
from app.tasks.celery_tasks import run_playbook_test
task = run_playbook_test.delay(playbook_id, preset, test_run.id)
return {
"message": "Тест запущен",
"test_run_id": test_run.id,
"task_id": task.id,
"websocket_url": f"/ws/playbook-test/{test_run.id}"
}
@router.post("/api/v1/playbooks/{playbook_id}/deploy")
async def deploy_playbook(
playbook_id: int,
inventory: Optional[str] = Form(None),
limit: Optional[str] = Form(None),
tags: Optional[str] = Form(None),
check: bool = Form(False),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Запуск деплоя playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
raise HTTPException(status_code=404, detail="Playbook не найден")
# Используем inventory из playbook или переданный
deploy_inventory = inventory or playbook.inventory
if not deploy_inventory:
raise HTTPException(
status_code=400,
detail="Inventory не указан. Укажите inventory в playbook или передайте его в запросе."
)
# Сохраняем запись о деплое
deployment = await PlaybookService.save_deployment(
db=db,
playbook_id=playbook_id,
inventory=deploy_inventory,
hosts=None, # Будет заполнено после парсинга inventory
status="running",
user=current_user.get("username")
)
# Запускаем деплой в фоне через Celery
from app.tasks.celery_tasks import run_playbook_deploy
task = run_playbook_deploy.delay(
playbook_id,
deploy_inventory,
limit,
tags,
check,
deployment.id
)
return {
"message": "Деплой запущен",
"deployment_id": deployment.id,
"task_id": task.id,
"websocket_url": f"/ws/playbook-deploy/{deployment.id}"
}
@router.websocket("/ws/playbook-test/{test_run_id}")
async def playbook_test_websocket(websocket: WebSocket, test_run_id: int):
"""WebSocket для live логов тестирования playbook"""
await websocket.accept()
try:
from app.db.session import get_async_db
from app.services.playbook_service import PlaybookService
from sqlalchemy.ext.asyncio import AsyncSession
# Получаем информацию о тесте
async for db in get_async_db():
test_run = await db.execute(
select(PlaybookTestRun).where(PlaybookTestRun.id == test_run_id)
)
test_run = test_run.scalar_one_or_none()
if not test_run:
await websocket.send_json({
"type": "error",
"data": f"Тест #{test_run_id} не найден"
})
await websocket.close()
return
playbook = await PlaybookService.get_playbook(db, test_run.playbook_id)
if not playbook:
await websocket.send_json({
"type": "error",
"data": "Playbook не найден"
})
await websocket.close()
return
# Подключаемся к Redis для получения логов из Celery
# Пока отправляем заглушку
await websocket.send_json({
"type": "info",
"data": f"Тестирование playbook '{playbook.name}' запущено..."
})
# TODO: Реализовать получение логов из Celery task
# Пока просто ждем и отправляем статус
import asyncio
await asyncio.sleep(1)
await websocket.send_json({
"type": "complete",
"status": "running",
"data": "Тест выполняется..."
})
except Exception as e:
await websocket.send_json({
"type": "error",
"data": f"Ошибка: {str(e)}"
})
await websocket.close()
@router.websocket("/ws/playbook-deploy/{deployment_id}")
async def playbook_deploy_websocket(websocket: WebSocket, deployment_id: int):
"""WebSocket для live логов деплоя playbook"""
await websocket.accept()
try:
from app.db.session import get_async_db
from app.services.playbook_service import PlaybookService
from sqlalchemy import select
from app.models.database import PlaybookDeployment
# Получаем информацию о деплое
async for db in get_async_db():
deployment = await db.execute(
select(PlaybookDeployment).where(PlaybookDeployment.id == deployment_id)
)
deployment = deployment.scalar_one_or_none()
if not deployment:
await websocket.send_json({
"type": "error",
"data": f"Деплой #{deployment_id} не найден"
})
await websocket.close()
return
playbook = await PlaybookService.get_playbook(db, deployment.playbook_id)
if not playbook:
await websocket.send_json({
"type": "error",
"data": "Playbook не найден"
})
await websocket.close()
return
# Подключаемся к Redis для получения логов из Celery
# Пока отправляем заглушку
await websocket.send_json({
"type": "info",
"data": f"Деплой playbook '{playbook.name}' запущен..."
})
# TODO: Реализовать получение логов из Celery task
# Пока просто ждем и отправляем статус
import asyncio
await asyncio.sleep(1)
await websocket.send_json({
"type": "complete",
"status": "running",
"data": "Деплой выполняется..."
})
except Exception as e:
await websocket.send_json({
"type": "error",
"data": f"Ошибка: {str(e)}"
})
await websocket.close()

View File

@@ -1,472 +0,0 @@
"""
API endpoints для управления preset'ами
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form, Depends, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Dict, Optional
import yaml
import json
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.services.preset_service import PresetService
from app.db.session import get_async_db
from app.auth.deps import get_current_user
logger = logging.getLogger(__name__)
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
@router.get("/presets", response_class=HTMLResponse)
async def list_presets(
request: Request,
page: int = 1,
per_page: int = 10,
search: Optional[str] = None,
category: Optional[str] = None,
db: AsyncSession = Depends(get_async_db)
):
"""Страница списка preset'ов с пагинацией"""
presets = await PresetService.get_all_presets(db, category=category)
# Фильтрация по поиску
if search:
search_lower = search.lower()
presets = [
p for p in presets
if search_lower in p.get("name", "").lower() or
search_lower in (p.get("description", "") or "").lower()
]
# Пагинация
total = len(presets)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = max(1, min(page, total_pages))
start = (page - 1) * per_page
end = start + per_page
paginated_presets = presets[start:end]
return templates.TemplateResponse(
"pages/presets/list.html",
{
"request": request,
"presets": paginated_presets,
"total": total,
"page": page,
"per_page": per_page,
"total_pages": total_pages,
"search": search or "",
"category": category or ""
}
)
@router.get("/api/v1/presets", response_model=List[Dict])
async def get_presets_api(
category: Optional[str] = None,
db: AsyncSession = Depends(get_async_db)
):
"""API endpoint для получения списка preset'ов"""
return await PresetService.get_all_presets(db, category=category)
@router.get("/presets/create", response_class=HTMLResponse)
async def create_preset_page(
request: Request,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Страница создания preset'а"""
from app.services.dockerfile_service import DockerfileService
# Загружаем список Dockerfiles из БД
dockerfiles = await DockerfileService.list_dockerfiles(db, status="active")
dockerfiles_list = [
{
"id": d.id,
"name": d.name,
"description": d.description,
"base_image": d.base_image,
"tags": d.tags,
"status": d.status
}
for d in dockerfiles
]
return templates.TemplateResponse(
"pages/presets/create.html",
{
"request": request,
"dockerfiles": dockerfiles_list
}
)
@router.get("/api/v1/presets/{preset_name}")
async def get_preset_api(
preset_name: str,
category: str = "main",
db: AsyncSession = Depends(get_async_db)
):
"""Получение preset'а по имени"""
preset = await PresetService.get_preset(db, preset_name, category)
if not preset:
raise HTTPException(status_code=404, detail=f"Preset '{preset_name}' не найден")
return {
"name": preset.name,
"category": preset.category,
"description": preset.description,
"content": preset.content,
"data": yaml.safe_load(preset.content) if preset.content else {}
}
@router.get("/presets/{preset_name}", response_class=HTMLResponse)
async def preset_detail(
request: Request,
preset_name: str,
category: str = "main",
db: AsyncSession = Depends(get_async_db)
):
"""Страница деталей preset'а"""
try:
preset = await PresetService.get_preset_dict(db, preset_name, category)
return templates.TemplateResponse(
"pages/presets/detail.html",
{
"request": request,
"preset": preset
}
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.post("/api/v1/presets/create")
async def create_preset_api(
preset_name: str = Form(...),
description: str = Form(""),
category: str = Form("main"),
hosts: str = Form(""),
images: str = Form(""),
systemd_defaults: str = Form(""),
db: AsyncSession = Depends(get_async_db)
):
"""API endpoint для создания preset'а"""
try:
hosts_list = []
if hosts:
hosts_list = json.loads(hosts)
images_dict = {}
if images:
images_dict = json.loads(images)
systemd_defaults_dict = {}
if systemd_defaults:
systemd_defaults_dict = json.loads(systemd_defaults)
preset = await PresetService.create_preset(
db=db,
preset_name=preset_name,
description=description,
hosts=hosts_list,
category=category,
images=images_dict,
systemd_defaults=systemd_defaults_dict
)
return JSONResponse(content={
"success": True,
"preset_name": preset.name,
"message": f"Preset '{preset_name}' успешно создан"
}, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при создании preset'а: {str(e)}")
@router.get("/presets/{preset_name}/edit", response_class=HTMLResponse)
async def edit_preset_page(
request: Request,
preset_name: str,
category: str = "main",
db: AsyncSession = Depends(get_async_db)
):
"""Страница редактирования preset'а"""
try:
preset = await PresetService.get_preset_dict(db, preset_name, category)
return templates.TemplateResponse(
"pages/presets/edit.html",
{
"request": request,
"preset": preset
}
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.post("/api/v1/presets/{preset_name}/update")
async def update_preset_api(
preset_name: str,
description: str = Form(""),
category: str = Form("main"),
docker_network: str = Form("labnet"),
hosts: str = Form(""),
images: str = Form(""),
systemd_defaults: str = Form(""),
kind_clusters: str = Form(""),
content: Optional[str] = Form(None),
db: AsyncSession = Depends(get_async_db)
):
"""API endpoint для обновления preset'а"""
try:
# Если передан content (YAML), используем старый метод
if content:
preset = await PresetService.update_preset(
db=db,
preset_name=preset_name,
content=content,
category=category
)
return JSONResponse(content={
"success": True,
"preset_name": preset.name,
"message": f"Preset '{preset_name}' успешно обновлен"
})
# Новый метод - из формы
hosts_list = []
if hosts:
hosts_list = json.loads(hosts)
images_dict = {}
if images:
images_dict = json.loads(images)
systemd_defaults_dict = {}
if systemd_defaults:
systemd_defaults_dict = json.loads(systemd_defaults)
kind_clusters_list = []
if kind_clusters:
kind_clusters_list = json.loads(kind_clusters)
preset = await PresetService.update_preset_from_form(
db=db,
preset_name=preset_name,
description=description,
category=category,
docker_network=docker_network,
hosts=hosts_list,
images=images_dict,
systemd_defaults=systemd_defaults_dict,
kind_clusters=kind_clusters_list
)
return JSONResponse(content={
"success": True,
"preset_name": preset.name,
"message": f"Preset '{preset_name}' успешно обновлен"
})
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при обновлении preset'а: {str(e)}")
@router.delete("/api/v1/presets/{preset_name}")
async def delete_preset_api(
preset_name: str,
category: str = "main",
db: AsyncSession = Depends(get_async_db)
):
"""API endpoint для удаления preset'а"""
try:
await PresetService.delete_preset(db, preset_name, category)
return JSONResponse(content={
"success": True,
"preset_name": preset_name,
"message": f"Preset '{preset_name}' успешно удален"
})
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при удалении preset'а: {str(e)}")
@router.websocket("/ws/preset/test/{preset_name}")
async def preset_test_websocket(websocket: WebSocket, preset_name: str, category: str = "main"):
"""WebSocket для live логов тестирования preset'а"""
await websocket.accept()
# Используем класс для хранения ссылки на контейнер
class ContainerRef:
def __init__(self):
self.container = None
def set(self, container):
self.container = container
def get(self):
return self.container
container_ref = ContainerRef()
executor = None
stop_requested = False
try:
# Получаем preset из БД
async for db in get_async_db():
preset = await PresetService.get_preset(db, preset_name, category)
if not preset:
await websocket.send_json({
"type": "error",
"data": f"Preset '{preset_name}' не найден"
})
await websocket.close()
return
preset_content = preset.content
break
# Запуск тестирования preset'а
from app.core.molecule_executor import MoleculeExecutor
executor = MoleculeExecutor()
# Создаем временный файл preset'а из БД
executor.create_temp_preset_file(preset_name, preset_content, category)
await websocket.send_json({
"type": "info",
"data": f"🚀 Запуск тестирования preset'а '{preset_name}'..."
})
# Создаем задачу для мониторинга сообщений от клиента (стоп)
import asyncio
async def monitor_stop():
nonlocal stop_requested
try:
while True:
try:
data = await asyncio.wait_for(websocket.receive_json(), timeout=1.0)
action = data.get("action")
if action == "stop":
stop_requested = True
cont = container_ref.get()
if cont:
try:
cont.stop()
await websocket.send_json({
"type": "info",
"data": "⏹️ Остановка контейнера..."
})
except Exception as e:
logger.error(f"Error stopping container: {e}")
break
except asyncio.TimeoutError:
continue
except WebSocketDisconnect:
stop_requested = True
break
except Exception:
pass
monitor_task = asyncio.create_task(monitor_stop())
# Запускаем тест (без указания роли - тестируем все роли)
try:
async for line in executor.test_role(
role_name=None,
preset_name=preset_name,
preset_content=preset_content,
preset_category=category,
stream=True,
stop_event=lambda: stop_requested,
container_ref=container_ref
):
if stop_requested:
break
line = line.rstrip()
if not line:
continue
log_type = executor.detect_log_level(line)
try:
await websocket.send_json({
"type": "log",
"level": log_type,
"data": line
})
except (WebSocketDisconnect, Exception) as e:
# Соединение закрыто - не пытаемся больше отправлять
stop_requested = True
logger.debug(f"WebSocket closed during log send: {e}")
break
# Отправляем финальное сообщение только если соединение открыто
try:
if not stop_requested:
await websocket.send_json({
"type": "complete",
"status": "success",
"data": "✅ Тестирование preset'а завершено"
})
else:
await websocket.send_json({
"type": "complete",
"status": "stopped",
"data": "⏹️ Тестирование остановлено пользователем"
})
except (WebSocketDisconnect, Exception):
# Соединение уже закрыто - это нормально
pass
except GeneratorExit:
# Генератор закрыт, это нормально при закрытии WebSocket
stop_requested = True
finally:
monitor_task.cancel()
try:
await monitor_task
except asyncio.CancelledError:
pass
# Удаляем временный файл
if executor and preset_name in executor._temp_preset_files:
try:
executor._temp_preset_files[preset_name].unlink()
del executor._temp_preset_files[preset_name]
except:
pass
except WebSocketDisconnect:
# Соединение закрыто клиентом - это нормально
pass
except GeneratorExit:
# Генератор закрыт - это нормально
pass
except Exception as e:
import traceback
error_msg = f"❌ Ошибка: {str(e)}"
try:
await websocket.send_json({
"type": "error",
"data": error_msg
})
except:
pass
logger.error(f"Error in preset_test_websocket: {e}\n{traceback.format_exc()}")
finally:
try:
await websocket.close()
except:
pass

View File

@@ -1,217 +0,0 @@
"""
API endpoints для управления профилем пользователя
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Depends, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from app.db.session import get_async_db
from app.services.user_service import UserService
from app.models.user import User, UserProfile
from app.models.database import Preset, Dockerfile, Playbook, CommandHistory
from app.auth.deps import get_current_user
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
@router.get("/profile", response_class=HTMLResponse)
async def profile_page(
request: Request,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Страница профиля пользователя"""
user = await UserService.get_user_by_username(db, current_user.get("username"))
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Получаем или создаем профиль
result = await db.execute(select(UserProfile).where(UserProfile.user_id == user.id))
profile = result.scalar_one_or_none()
if not profile:
profile = UserProfile(user_id=user.id)
db.add(profile)
await db.commit()
await db.refresh(profile)
# Получаем статистику пользователя
username = user.username
# Количество созданных preset'ов
presets_count = await db.execute(
select(func.count(Preset.id)).where(Preset.created_by == username)
)
presets_count = presets_count.scalar() or 0
# Количество созданных Dockerfile'ов
dockerfiles_count = await db.execute(
select(func.count(Dockerfile.id)).where(Dockerfile.created_by == username)
)
dockerfiles_count = dockerfiles_count.scalar() or 0
# Количество созданных playbook'ов
playbooks_count = await db.execute(
select(func.count(Playbook.id)).where(Playbook.created_by == username)
)
playbooks_count = playbooks_count.scalar() or 0
# Количество выполненных команд
commands_count = await db.execute(
select(func.count(CommandHistory.id)).where(CommandHistory.user == username)
)
commands_count = commands_count.scalar() or 0
# Количество успешных тестов
successful_tests = await db.execute(
select(func.count(CommandHistory.id)).where(
CommandHistory.user == username,
CommandHistory.command_type == "test",
CommandHistory.status == "success"
)
)
successful_tests = successful_tests.scalar() or 0
return templates.TemplateResponse(
"pages/profile/index.html",
{
"request": request,
"user": user,
"profile": profile,
"stats": {
"presets": presets_count,
"dockerfiles": dockerfiles_count,
"playbooks": playbooks_count,
"commands": commands_count,
"successful_tests": successful_tests
}
}
)
@router.post("/api/v1/profile")
async def update_profile(
request: Request,
email: Optional[str] = Form(None),
full_name: Optional[str] = Form(None),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Обновление профиля пользователя"""
user = await UserService.get_user_by_username(db, current_user.get("username"))
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Получаем или создаем профиль
result = await db.execute(select(UserProfile).where(UserProfile.user_id == user.id))
profile = result.scalar_one_or_none()
if not profile:
profile = UserProfile(user_id=user.id)
db.add(profile)
if email:
profile.email = email
if full_name:
profile.full_name = full_name
await db.commit()
await db.refresh(profile)
return JSONResponse(content={
"success": True,
"message": "Профиль обновлен успешно"
})
@router.get("/profile/docker-settings", response_class=HTMLResponse)
async def docker_settings_page(
request: Request,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Страница настроек Docker (Harbor и Docker Hub)"""
user = await UserService.get_user_by_username(db, current_user.get("username"))
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Получаем или создаем профиль
result = await db.execute(select(UserProfile).where(UserProfile.user_id == user.id))
profile = result.scalar_one_or_none()
if not profile:
profile = UserProfile(user_id=user.id)
db.add(profile)
await db.commit()
await db.refresh(profile)
return templates.TemplateResponse(
"pages/profile/docker-settings.html",
{
"request": request,
"profile": profile
}
)
@router.post("/api/v1/profile/docker-settings")
async def update_docker_settings(
request: Request,
dockerhub_username: Optional[str] = Form(None),
dockerhub_password: Optional[str] = Form(None),
dockerhub_repository: Optional[str] = Form(None),
harbor_url: Optional[str] = Form(None),
harbor_username: Optional[str] = Form(None),
harbor_password: Optional[str] = Form(None),
harbor_project: Optional[str] = Form(None),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_async_db)
):
"""Обновление настроек Docker"""
user = await UserService.get_user_by_username(db, current_user.get("username"))
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Получаем или создаем профиль
result = await db.execute(select(UserProfile).where(UserProfile.user_id == user.id))
profile = result.scalar_one_or_none()
if not profile:
profile = UserProfile(user_id=user.id)
db.add(profile)
# Обновляем настройки Docker Hub
if dockerhub_username is not None:
profile.dockerhub_username = dockerhub_username
if dockerhub_password:
# TODO: Зашифровать пароль перед сохранением
profile.dockerhub_password = dockerhub_password
if dockerhub_repository is not None:
profile.dockerhub_repository = dockerhub_repository
# Обновляем настройки Harbor
if harbor_url is not None:
profile.harbor_url = harbor_url
if harbor_username is not None:
profile.harbor_username = harbor_username
if harbor_password:
# TODO: Зашифровать пароль перед сохранением
profile.harbor_password = harbor_password
if harbor_project is not None:
profile.harbor_project = harbor_project
await db.commit()
await db.refresh(profile)
return JSONResponse(content={
"success": True,
"message": "Настройки Docker обновлены успешно"
})

View File

@@ -1,312 +0,0 @@
"""
API endpoints для управления ролями
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Dict, List, Optional
import yaml
import json
from app.core.config import settings
from app.services.role_service import RoleService
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
role_service = RoleService()
def get_roles_list() -> List[Dict]:
"""Получение списка ролей"""
roles_dir = settings.PROJECT_ROOT / "roles"
if not roles_dir.exists():
return []
roles = []
for role_dir in roles_dir.iterdir():
if role_dir.is_dir():
role_info = {
"name": role_dir.name,
"path": str(role_dir),
"has_tasks": (role_dir / "tasks" / "main.yml").exists(),
"has_defaults": (role_dir / "defaults" / "main.yml").exists(),
"has_handlers": (role_dir / "handlers" / "main.yml").exists(),
"has_meta": (role_dir / "meta" / "main.yml").exists(),
"has_readme": (role_dir / "README.md").exists(),
"description": ""
}
# Чтение описания из meta/main.yml
meta_file = role_dir / "meta" / "main.yml"
if meta_file.exists():
try:
with open(meta_file) as f:
meta_data = yaml.safe_load(f)
if meta_data and isinstance(meta_data, dict):
role_info["description"] = meta_data.get("galaxy_info", {}).get("description", "")
role_info["author"] = meta_data.get("galaxy_info", {}).get("author", "")
role_info["platforms"] = meta_data.get("galaxy_info", {}).get("platforms", [])
except:
pass
roles.append(role_info)
return sorted(roles, key=lambda x: x["name"])
@router.get("/roles/create", response_class=HTMLResponse)
async def create_role_page(request: Request):
"""Страница создания роли"""
return templates.TemplateResponse(
"pages/roles/create.html",
{"request": request}
)
@router.get("/roles/{role_name}/edit", response_class=HTMLResponse)
async def edit_role_page(request: Request, role_name: str):
"""Страница редактирования роли"""
roles = get_roles_list()
role = next((r for r in roles if r["name"] == role_name), None)
if not role:
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
role_dir = settings.PROJECT_ROOT / "roles" / role_name
# Чтение всех файлов роли
files_content = {}
for file_type in ["tasks", "handlers", "defaults", "vars", "meta"]:
file_path = role_dir / file_type / "main.yml"
if file_path.exists():
files_content[file_type] = file_path.read_text()
else:
files_content[file_type] = ""
readme_content = ""
readme_file = role_dir / "README.md"
if readme_file.exists():
readme_content = readme_file.read_text()
else:
readme_content = ""
return templates.TemplateResponse(
"pages/roles/edit.html",
{
"request": request,
"role": role,
"files_content": files_content,
"readme_content": readme_content
}
)
@router.get("/roles", response_class=HTMLResponse)
async def list_roles(
request: Request,
page: int = 1,
per_page: int = 10,
search: Optional[str] = None
):
"""Страница списка ролей с пагинацией"""
"""Страница списка ролей с пагинацией"""
roles = get_roles_list()
# Фильтрация по поиску
if search:
search_lower = search.lower()
roles = [
r for r in roles
if search_lower in r["name"].lower() or
search_lower in (r.get("description", "") or "").lower()
]
# Пагинация
total = len(roles)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = max(1, min(page, total_pages))
start = (page - 1) * per_page
end = start + per_page
paginated_roles = roles[start:end]
return templates.TemplateResponse(
"pages/roles/list.html",
{
"request": request,
"roles": paginated_roles,
"total": total,
"page": page,
"per_page": per_page,
"total_pages": total_pages,
"search": search or ""
}
)
@router.get("/api/v1/roles")
async def get_roles_api():
"""API endpoint для получения списка ролей"""
return get_roles_list()
@router.get("/api/v1/roles/{role_name}")
async def get_role_info(role_name: str):
"""API endpoint для получения информации о роли"""
roles = get_roles_list()
role = next((r for r in roles if r["name"] == role_name), None)
if not role:
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
return role
@router.get("/roles/{role_name}", response_class=HTMLResponse)
async def role_detail(request: Request, role_name: str):
"""Страница деталей роли"""
roles = get_roles_list()
role = next((r for r in roles if r["name"] == role_name), None)
if not role:
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
# Чтение содержимого файлов
role_dir = settings.PROJECT_ROOT / "roles" / role_name
tasks_content = ""
tasks_file = role_dir / "tasks" / "main.yml"
if tasks_file.exists():
tasks_content = tasks_file.read_text()
defaults_content = ""
defaults_file = role_dir / "defaults" / "main.yml"
if defaults_file.exists():
defaults_content = defaults_file.read_text()
readme_content = ""
readme_file = role_dir / "README.md"
if readme_file.exists():
readme_content = readme_file.read_text()
return templates.TemplateResponse(
"pages/roles/detail.html",
{
"request": request,
"role": role,
"tasks_content": tasks_content,
"defaults_content": defaults_content,
"readme_content": readme_content
}
)
@router.post("/api/v1/roles/create")
async def create_role_api(
role_name: str = Form(...),
template: str = Form("default"),
description: str = Form(""),
platforms: str = Form(""), # JSON строка или через запятую
variables: str = Form("") # JSON строка
):
"""API endpoint для создания роли"""
try:
# Парсинг platforms
platforms_list = []
if platforms:
if platforms.startswith("["):
platforms_list = json.loads(platforms)
else:
platforms_list = [p.strip() for p in platforms.split(",") if p.strip()]
# Парсинг variables
variables_list = []
if variables:
variables_list = json.loads(variables)
result = role_service.create_role(
role_name=role_name,
template=template,
description=description,
platforms=platforms_list,
variables=variables_list
)
return JSONResponse(content=result, status_code=201)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при создании роли: {str(e)}")
@router.post("/api/v1/roles/{role_name}/update")
async def update_role_api(
role_name: str,
file_type: str = Form(...),
content: str = Form(...)
):
"""API endpoint для обновления файла роли"""
try:
role_dir = settings.PROJECT_ROOT / "roles" / role_name
if not role_dir.exists():
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
# Определение пути к файлу
if file_type == "readme":
file_path = role_dir / "README.md"
elif file_type in ["tasks", "handlers", "defaults", "vars", "meta"]:
file_path = role_dir / file_type / "main.yml"
file_path.parent.mkdir(parents=True, exist_ok=True)
else:
raise HTTPException(status_code=400, detail=f"Неверный тип файла: {file_type}")
# Сохранение файла
file_path.write_text(content)
return JSONResponse(content={
"success": True,
"message": f"Файл {file_type} успешно обновлен"
})
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при обновлении роли: {str(e)}")
@router.get("/roles/{role_name}/deploy", response_class=HTMLResponse)
async def deploy_role_page(request: Request, role_name: str):
"""Страница деплоя роли"""
roles = get_roles_list()
role = next((r for r in roles if r["name"] == role_name), None)
if not role:
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
# Проверка наличия inventory
inventory_file = settings.PROJECT_ROOT / "inventory" / "hosts.ini"
inventory_exists = inventory_file.exists()
# Чтение inventory если существует
inventory_content = ""
if inventory_exists:
try:
inventory_content = inventory_file.read_text()
except Exception:
pass
# Проверка наличия deploy.yml
deploy_playbook = settings.PROJECT_ROOT / "roles" / "deploy.yml"
deploy_playbook_exists = deploy_playbook.exists()
return templates.TemplateResponse(
"pages/roles/deploy.html",
{
"request": request,
"role": role,
"role_name": role_name,
"inventory_exists": inventory_exists,
"inventory_content": inventory_content,
"deploy_playbook_exists": deploy_playbook_exists
}
)

View File

@@ -1,69 +0,0 @@
"""
API endpoints для статистики
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
from pathlib import Path
from app.core.config import settings
router = APIRouter()
@router.get("/")
async def get_stats():
"""Общая статистика"""
roles_dir = settings.PROJECT_ROOT / "roles"
roles_count = 0
if roles_dir.exists():
roles = [d for d in roles_dir.iterdir()
if d.is_dir() and d.name != "deploy.yml"]
roles_count = len(roles)
return {
"roles": roles_count,
"tests": 0, # TODO: из БД
"success": 0, # TODO: из БД
"docker": 0 # TODO: из Docker API
}
@router.get("/roles", response_class=HTMLResponse)
async def get_roles_count():
"""Количество ролей"""
try:
roles_dir = settings.PROJECT_ROOT / "roles"
if not roles_dir.exists():
return "0"
# Подсчет ролей (исключая deploy.yml)
roles = [d for d in roles_dir.iterdir()
if d.is_dir() and d.name != "deploy.yml" and not d.name.startswith(".")]
return str(len(roles))
except Exception as e:
# В случае ошибки возвращаем 0
return "0"
@router.get("/tests", response_class=HTMLResponse)
async def get_tests_count():
"""Количество тестов"""
# TODO: Реализовать через БД
return "0"
@router.get("/success", response_class=HTMLResponse)
async def get_success_count():
"""Количество успешных тестов"""
# TODO: Реализовать через БД
return "0"
@router.get("/docker", response_class=HTMLResponse)
async def get_docker_count():
"""Количество Docker образов"""
# TODO: Реализовать через Docker API
return "0"

View File

@@ -1,273 +0,0 @@
"""
API endpoints для тестирования ролей
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect, Depends
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import Dict, Optional
import json
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.molecule_executor import MoleculeExecutor
from app.services.preset_service import PresetService
from app.db.session import get_async_db
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
molecule_executor = MoleculeExecutor()
@router.get("/roles/{role_name}/test", response_class=HTMLResponse)
async def test_role_page(
request: Request,
role_name: str,
db: AsyncSession = Depends(get_async_db)
):
"""Страница тестирования роли"""
# Проверка существования роли
roles_dir = settings.PROJECT_ROOT / "roles" / role_name
if not roles_dir.exists():
from fastapi import HTTPException
raise HTTPException(status_code=404, detail=f"Роль '{role_name}' не найдена")
# Получение списка preset'ов из БД
presets = await PresetService.get_all_presets(db)
return templates.TemplateResponse(
"pages/roles/test.html",
{
"request": request,
"role_name": role_name,
"presets": presets
}
)
@router.post("/api/v1/roles/{role_name}/test")
async def start_role_test(
role_name: str,
preset: str = "default",
variables: Optional[str] = None
):
"""Запуск теста роли"""
# TODO: Запуск через Celery для фонового выполнения
# Пока просто возвращаем информацию
return {
"success": True,
"role_name": role_name,
"preset": preset,
"message": "Тест запущен",
"test_id": f"{role_name}-{preset}"
}
@router.get("/tests", response_class=HTMLResponse)
async def tests_history_page(
request: Request,
role_name: Optional[str] = None,
page: int = 1,
per_page: int = 20,
db: AsyncSession = Depends(get_async_db)
):
"""Страница истории тестов"""
try:
from app.services.history_service import HistoryService
from sqlalchemy import select, func
from app.models.database import CommandHistory, TestResult
history_service = HistoryService()
# Получение тестов из БД
query = select(CommandHistory).where(
CommandHistory.command_type == "test"
)
if role_name:
query = query.where(CommandHistory.command.like(f"%{role_name}%"))
query = query.order_by(CommandHistory.created_at.desc())
# Подсчет общего количества
count_query = select(func.count(CommandHistory.id)).where(
CommandHistory.command_type == "test"
)
if role_name:
count_query = count_query.where(CommandHistory.command.like(f"%{role_name}%"))
total = (await db.execute(count_query)).scalar() or 0
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = max(1, min(page, total_pages))
# Пагинация
offset = (page - 1) * per_page
query = query.offset(offset).limit(per_page)
result = await db.execute(query)
tests = result.scalars().all()
# Получение списка ролей для фильтра
roles_dir = settings.PROJECT_ROOT / "roles"
roles = []
if roles_dir.exists():
for role_dir in roles_dir.iterdir():
if role_dir.is_dir() and role_dir.name != "deploy.yml":
roles.append(role_dir.name)
return templates.TemplateResponse(
"pages/tests/index.html",
{
"request": request,
"tests": tests,
"roles": sorted(roles),
"role_name": role_name or "",
"total": total,
"page": page,
"per_page": per_page,
"total_pages": total_pages
}
)
except Exception as e:
import logging
logging.error(f"Error loading tests history: {e}", exc_info=True)
# Если база данных не настроена, показываем пустую страницу
return templates.TemplateResponse(
"pages/tests/index.html",
{
"request": request,
"tests": [],
"roles": [],
"role_name": role_name or "",
"total": 0,
"page": 1,
"per_page": per_page,
"total_pages": 1,
"error": "База данных не настроена или недоступна"
}
)
@router.get("/api/v1/tests/recent")
async def get_recent_tests(limit: int = 10):
"""Получение последних тестов"""
try:
from app.services.history_service import HistoryService
history_service = HistoryService()
tests = history_service.get_command_history(
limit=limit,
command_type="test"
)
return tests
except Exception:
# Если база данных не настроена, возвращаем пустой список
return []
@router.websocket("/ws/test/{test_id}")
async def test_websocket(websocket: WebSocket, test_id: str):
"""WebSocket для live логов тестирования"""
await websocket.accept()
try:
# Парсинг test_id (формат: role_name-preset или role_name-preset-category)
parts = test_id.rsplit("-", 2)
if len(parts) < 2:
await websocket.send_json({
"type": "error",
"data": "Неверный формат test_id. Ожидается: role_name-preset или role_name-preset-category"
})
await websocket.close()
return
role_name = parts[0]
preset_name = parts[1] if len(parts) > 1 else "default"
preset_category = parts[2] if len(parts) > 2 else "main"
# Проверка существования роли
roles_dir = settings.PROJECT_ROOT / "roles" / role_name
if not roles_dir.exists():
await websocket.send_json({
"type": "error",
"data": f"Роль '{role_name}' не найдена"
})
await websocket.close()
return
# Получаем preset из БД
async for db in get_async_db():
preset = await PresetService.get_preset(db, preset_name, preset_category)
if not preset:
await websocket.send_json({
"type": "error",
"data": f"Preset '{preset_name}' не найден"
})
await websocket.close()
return
preset_content = preset.content
break
# Отправка начального сообщения
await websocket.send_json({
"type": "info",
"data": f"🚀 Запуск теста роли '{role_name}' с preset '{preset_name}'..."
})
# Создаем временный файл preset'а из БД
molecule_executor.create_temp_preset_file(preset_name, preset_content, preset_category)
# Запуск теста через MoleculeExecutor (без Makefile)
async for line in molecule_executor.test_role(
role_name=role_name,
preset_name=preset_name,
preset_content=preset_content,
preset_category=preset_category,
stream=True
):
# Очистка строки от лишних символов
line = line.rstrip()
if not line:
continue
# Определение типа лога
log_type = molecule_executor.detect_log_level(line)
await websocket.send_json({
"type": "log",
"level": log_type,
"data": line
})
# Завершение
await websocket.send_json({
"type": "complete",
"status": "success",
"data": "✅ Тест завершен успешно"
})
# Удаляем временный файл
if preset_name in molecule_executor._temp_preset_files:
try:
molecule_executor._temp_preset_files[preset_name].unlink()
del molecule_executor._temp_preset_files[preset_name]
except:
pass
except WebSocketDisconnect:
pass
except Exception as e:
import traceback
error_msg = f"❌ Ошибка: {str(e)}\n{traceback.format_exc()}"
await websocket.send_json({
"type": "error",
"data": error_msg
})
finally:
try:
await websocket.close()
except:
pass

View File

@@ -1,113 +0,0 @@
"""
API endpoints для управления Ansible Vault
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter, Request, HTTPException, Form
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from typing import List, Dict
from app.core.config import settings
router = APIRouter()
templates_path = Path(__file__).parent.parent.parent.parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))
@router.get("/vault", response_class=HTMLResponse)
async def vault_page(request: Request):
"""Страница управления Ansible Vault"""
vault_dir = settings.PROJECT_ROOT / "vault"
vault_files = []
if vault_dir.exists():
# Ищем все .yml и .yaml файлы
for pattern in ["*.yml", "*.yaml"]:
for vault_file in vault_dir.glob(pattern):
if vault_file.is_file():
try:
vault_files.append({
"name": vault_file.name,
"path": str(vault_file.relative_to(settings.PROJECT_ROOT)),
"size": vault_file.stat().st_size
})
except Exception:
pass
return templates.TemplateResponse(
"pages/vault/index.html",
{
"request": request,
"vault_files": vault_files,
"vault_dir": str(vault_dir.relative_to(settings.PROJECT_ROOT)) if vault_dir.exists() else None
}
)
@router.get("/api/v1/vault/files")
async def get_vault_files():
"""API endpoint для получения списка Vault файлов"""
vault_dir = settings.PROJECT_ROOT / "vault"
vault_files = []
if vault_dir.exists():
for vault_file in vault_dir.glob("*.yml"):
vault_files.append({
"name": vault_file.name,
"path": str(vault_file),
"size": vault_file.stat().st_size
})
return {"files": vault_files}
@router.post("/api/v1/vault/encrypt")
async def encrypt_string(request: Request):
"""API endpoint для шифрования строки через Vault"""
from fastapi import Body
from app.services.vault_service import VaultService
data = await request.json()
content = data.get("content", "")
if not content:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail="Содержимое не указано")
try:
vault_service = VaultService()
result = vault_service.encrypt_string(content)
return result
except ValueError as e:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
from fastapi import HTTPException
raise HTTPException(status_code=500, detail=f"Ошибка шифрования: {str(e)}")
@router.post("/api/v1/vault/decrypt")
async def decrypt_string(request: Request):
"""API endpoint для расшифровки строки из Vault"""
from fastapi import Body
from app.services.vault_service import VaultService
data = await request.json()
content = data.get("content", "")
if not content:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail="Содержимое не указано")
try:
vault_service = VaultService()
result = vault_service.decrypt_string(content)
return result
except ValueError as e:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
from fastapi import HTTPException
raise HTTPException(status_code=500, detail=f"Ошибка расшифровки: {str(e)}")

View File

@@ -1,36 +0,0 @@
"""
API роутер v1
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import APIRouter
# Создание основного роутера
api_router = APIRouter(prefix="/api/v1", tags=["api"])
# Импорт endpoints
from app.api.v1.endpoints import stats, roles, tests, presets, deploy, export, auth, docker, vault, k8s, playbooks, lint, profile
from app.api.v1.endpoints.import_role import router as import_router
from app.api.v1.endpoints.dockerfiles_api import router as dockerfiles_router
# Подключение роутеров
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(stats.router, prefix="/stats", tags=["stats"])
api_router.include_router(roles.router, tags=["roles"])
api_router.include_router(tests.router, tags=["tests"])
api_router.include_router(presets.router, tags=["presets"])
api_router.include_router(deploy.router, tags=["deploy"])
api_router.include_router(export.router, tags=["export"])
api_router.include_router(import_router, tags=["import"])
api_router.include_router(docker.router, tags=["docker"])
api_router.include_router(vault.router, tags=["vault"])
api_router.include_router(k8s.router, tags=["k8s"])
api_router.include_router(playbooks.router, tags=["playbooks"])
api_router.include_router(dockerfiles_router, tags=["dockerfiles"])
api_router.include_router(lint.router, tags=["lint"])
# profile.router подключен напрямую к app в main.py, чтобы маршруты были доступны по /profile, а не /api/v1/profile
# TODO: Добавить остальные роутеры по мере реализации
# api_router.include_router(docker.router, prefix="/docker", tags=["docker"])
# api_router.include_router(vault.router, prefix="/vault", tags=["vault"])

View File

@@ -1,3 +0,0 @@
# App package
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru

View File

@@ -1 +0,0 @@
# Auth package

View File

@@ -1,84 +0,0 @@
"""
Зависимости для аутентификации
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from typing import Optional
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from app.auth.security import decode_access_token
security = HTTPBearer(auto_error=False)
async def get_current_user(
request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
):
"""Получение текущего пользователя из токена (cookie или Authorization header)"""
token = None
# Сначала проверяем, есть ли пользователь в request.state (установлен middleware)
if hasattr(request.state, 'user') and request.state.user:
return {"username": request.state.user}
# Проверяем cookie
if request.cookies and "access_token" in request.cookies:
token = request.cookies.get("access_token")
# Проверяем заголовок Authorization
elif credentials:
token = credentials.credentials
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Требуется аутентификация"
)
payload = decode_access_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверный токен аутентификации"
)
username: str = payload.get("sub")
if username is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверный токен аутентификации"
)
return {"username": username}
# Для простоты пока без реальной БД пользователей
# В будущем можно добавить модель User
async def get_current_user_optional(
request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
):
"""Опциональное получение пользователя (для публичных страниц)"""
try:
# Сначала проверяем, есть ли пользователь в request.state (установлен middleware)
if hasattr(request.state, 'user') and request.state.user:
return {"username": request.state.user}
# Проверяем cookie
token = None
if request.cookies and "access_token" in request.cookies:
token = request.cookies.get("access_token")
# Проверяем заголовок Authorization
elif credentials:
token = credentials.credentials
if token:
payload = decode_access_token(token)
if payload:
username: str = payload.get("sub")
if username:
return {"username": username}
except:
pass
return None

Some files were not shown because too many files have changed in this diff Show More