Обновление конфигурации Ansible: добавлены новые пресеты, улучшен Makefile, добавлена документация
This commit is contained in:
57
Dockerfile
Normal file
57
Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# AnsibleTemplate - Dockerfile для тестирования
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
FROM quay.io/ansible/creator-ee:latest
|
||||||
|
|
||||||
|
# Установка дополнительных зависимостей
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# Обновление системы и установка необходимых пакетов
|
||||||
|
RUN dnf update -y && \
|
||||||
|
dnf install -y \
|
||||||
|
python3-pip \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
jq \
|
||||||
|
ca-certificates \
|
||||||
|
iproute2 \
|
||||||
|
iputils \
|
||||||
|
procps-ng \
|
||||||
|
net-tools \
|
||||||
|
sudo \
|
||||||
|
vim \
|
||||||
|
&& dnf clean all
|
||||||
|
|
||||||
|
# Установка Python пакетов
|
||||||
|
RUN pip3 install --upgrade pip && \
|
||||||
|
pip3 install \
|
||||||
|
ansible-lint \
|
||||||
|
molecule \
|
||||||
|
molecule-docker \
|
||||||
|
docker-compose
|
||||||
|
|
||||||
|
# Создание рабочей директории
|
||||||
|
WORKDIR /ansible
|
||||||
|
|
||||||
|
# Копирование файлов проекта
|
||||||
|
COPY . /ansible/
|
||||||
|
|
||||||
|
# Установка прав доступа
|
||||||
|
RUN chmod +x /ansible/scripts/*.sh 2>/dev/null || true
|
||||||
|
|
||||||
|
# Переключение на пользователя ansible
|
||||||
|
USER ansible
|
||||||
|
|
||||||
|
# Установка Ansible коллекций
|
||||||
|
RUN ansible-galaxy collection install -r requirements.yml --force
|
||||||
|
|
||||||
|
# Настройка переменных окружения
|
||||||
|
ENV ANSIBLE_FORCE_COLOR=1
|
||||||
|
ENV ANSIBLE_STDOUT_CALLBACK=yaml
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Команда по умолчанию
|
||||||
|
CMD ["/bin/bash"]
|
||||||
187
Makefile
187
Makefile
@@ -4,6 +4,8 @@
|
|||||||
# Сайт: https://devops.org.ru
|
# Сайт: https://devops.org.ru
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ЦВЕТА ДЛЯ ВЫВОДА
|
# ЦВЕТА ДЛЯ ВЫВОДА
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -24,113 +26,136 @@ VERSION ?= 0.1.0
|
|||||||
AUTHOR ?= "Сергей Антропов"
|
AUTHOR ?= "Сергей Антропов"
|
||||||
SITE ?= "https://devops.org.ru"
|
SITE ?= "https://devops.org.ru"
|
||||||
DOCKER_IMAGE ?= quay.io/ansible/creator-ee:latest
|
DOCKER_IMAGE ?= quay.io/ansible/creator-ee:latest
|
||||||
|
DOCKER_DIND_IMAGE ?= docker:27-dind
|
||||||
CONTAINER_NAME ?= ansible-controller
|
CONTAINER_NAME ?= ansible-controller
|
||||||
|
|
||||||
.PHONY: role molecule vault git help
|
.PHONY: role molecule vault git docker help
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
# Работа с ролями
|
|
||||||
####################################################################################################
|
|
||||||
role:
|
role:
|
||||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
lint) \
|
lint) \
|
||||||
clear; \
|
echo "🔍 Проверка синтаксиса ролей ..."; \
|
||||||
echo "$(BLUE)🔍 Проверка синтаксиса ролей ...$(RESET)"; \
|
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace -e ANSIBLE_FORCE_COLOR=1 $(DOCKER_IMAGE) bash -c "ansible-lint roles/ --config-file .ansible-lint || true"; \
|
||||||
echo ""; \
|
echo "✅ Lint завершен";; \
|
||||||
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
|
|
||||||
-e ANSIBLE_FORCE_COLOR=1 \
|
|
||||||
$(DOCKER_IMAGE) \
|
|
||||||
bash -c "ansible-lint roles/ --config-file .ansible-lint" || echo "$(GREEN)✅ Lint завершен с предупреждениями$(RESET)";; \
|
|
||||||
test) \
|
test) \
|
||||||
clear; \
|
echo "🚀 Тестирование ролей ..."; \
|
||||||
echo "$(PURPLE)🚀 Тестирование ролей ...$(RESET)"; \
|
|
||||||
PRESET="default"; \
|
PRESET="default"; \
|
||||||
if [ -n "$(word 3, $(MAKECMDGOALS))" ]; then \
|
ARGS="$(wordlist 3,10,$(MAKECMDGOALS))"; \
|
||||||
PRESET="$(word 3, $(MAKECMDGOALS))"; \
|
if [ -n "$$ARGS" ]; then \
|
||||||
echo "$(CYAN)📋 Используется пресет: $(YELLOW)$$PRESET$(RESET)"; \
|
PRESET="$$(echo $$ARGS | cut -d' ' -f1)"; \
|
||||||
cp molecule/presets/$$PRESET.yml molecule/presets/default.yml; \
|
fi; \
|
||||||
else \
|
echo "📋 Используется пресет: $$PRESET"; \
|
||||||
echo "$(CYAN)📋 Используется пресет: $(YELLOW)default$(RESET)"; \
|
if [ ! -f "molecule/presets/$$PRESET.yml" ]; then \
|
||||||
|
echo "❌ Ошибка: Пресет '$$PRESET' не найден!"; \
|
||||||
|
echo "💡 Доступные пресеты:"; \
|
||||||
|
ls -1 molecule/presets/*.yml 2>/dev/null | sed 's|molecule/presets/||g' | sed 's|\.yml||g' | sed 's/^/ - /' || echo " ⚠️ Пресеты не найдены"; \
|
||||||
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
|
if [ "$$PRESET" = "standart" ]; then \
|
||||||
-e ANSIBLE_FORCE_COLOR=1 \
|
./scripts/test-standart.sh; \
|
||||||
$(DOCKER_IMAGE) \
|
else \
|
||||||
bash -c "cd molecule/default && ansible-playbook -i localhost, site.yml --connection=local" || echo "$(GREEN)✅ Тестирование завершено$(RESET)";; \
|
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e ANSIBLE_FORCE_COLOR=1 \
|
||||||
|
-e MOLECULE_PRESET=$$PRESET \
|
||||||
|
$(DOCKER_IMAGE) \
|
||||||
|
bash -c "cd molecule/default && ansible-playbook -i localhost, create.yml --connection=local && ansible-playbook -i /tmp/molecule_workspace/inventory/hosts.ini site.yml && ansible-playbook -i localhost, destroy.yml --connection=local" || echo "✅ Тестирование завершено"; \
|
||||||
|
fi;; \
|
||||||
presets) \
|
presets) \
|
||||||
clear; \
|
echo "📋 Доступные пресеты:"; \
|
||||||
echo "$(CYAN)📋 Доступные пресеты:$(RESET)"; \
|
|
||||||
echo ""; \
|
echo ""; \
|
||||||
|
preset_count=0; \
|
||||||
for preset in molecule/presets/*.yml; do \
|
for preset in molecule/presets/*.yml; do \
|
||||||
if [ -f "$$preset" ]; then \
|
if [ -f "$$preset" ]; then \
|
||||||
preset_name=$$(basename "$$preset" .yml); \
|
preset_name=$$(basename "$$preset" .yml); \
|
||||||
printf " $(BLUE)📄 %s$(RESET)\n" "$$preset_name"; \
|
preset_desc=$$(grep -E "^#.*пресет|^#.*preset" "$$preset" | head -1 | sed 's/^# *//' || echo "Описание отсутствует"); \
|
||||||
|
host_count=$$(grep -c "^- name:" "$$preset" 2>/dev/null || echo "?"); \
|
||||||
|
printf " 📄 %s - %s (%s хостов)\n" "$$preset_name" "$$preset_desc" "$$host_count"; \
|
||||||
|
preset_count=$$((preset_count + 1)); \
|
||||||
fi; \
|
fi; \
|
||||||
done || echo " $(YELLOW)⚠️ Пресеты не найдены$(RESET)"; \
|
done; \
|
||||||
echo "";; \
|
if [ $$preset_count -eq 0 ]; then \
|
||||||
|
echo " ⚠️ Пресеты не найдены"; \
|
||||||
|
fi; \
|
||||||
|
echo ""; \
|
||||||
|
echo "💡 Использование:"; \
|
||||||
|
echo " make role test - с default preset"; \
|
||||||
|
echo " make role test [preset_name] - с любым preset"; \
|
||||||
|
echo " make role test minimal - с minimal preset"; \
|
||||||
|
echo " make role test standard - со standard preset"; \
|
||||||
|
echo " make role test docker - с docker preset"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "💡 Примеры:"; \
|
||||||
|
echo " make role test # default preset"; \
|
||||||
|
echo " make role test minimal # minimal preset"; \
|
||||||
|
echo " make role test my-custom-preset # любой preset";; \
|
||||||
deploy) \
|
deploy) \
|
||||||
clear; \
|
echo "🚀 Развертывание ролей на реальные серверы..."; \
|
||||||
echo "$(PURPLE)🚀 Развертывание ролей на реальные серверы...$(RESET)"; \
|
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo "$(YELLOW)💡 Примеры использования:$(RESET)"; \
|
if [ ! -f "inventory/hosts.ini" ]; then \
|
||||||
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml$(RESET)"; \
|
echo "❌ Ошибка: Файл inventory/hosts.ini не найден!"; \
|
||||||
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml --limit web_servers$(RESET)"; \
|
echo "💡 Создайте файл inventory/hosts.ini с вашими серверами"; \
|
||||||
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml --check$(RESET)"; \
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
echo "📋 Используется inventory: inventory/hosts.ini"; \
|
||||||
|
echo "📄 Содержимое inventory:"; \
|
||||||
|
cat inventory/hosts.ini; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo "$(CYAN)📄 Доступные playbook:$(RESET)"; \
|
echo "🚀 Запуск развертывания..."; \
|
||||||
ls -la *.yml 2>/dev/null | grep -v molecule || echo " $(BLUE)📄 deploy.yml - основной playbook для развертывания$(RESET)";; \
|
ansible-playbook -i inventory/hosts.ini deploy.yml --check; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Продолжить развертывание? (y/N): " confirm; \
|
||||||
|
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||||
|
ansible-playbook -i inventory/hosts.ini deploy.yml; \
|
||||||
|
else \
|
||||||
|
echo "❌ Развертывание отменено"; \
|
||||||
|
fi;; \
|
||||||
*) \
|
*) \
|
||||||
clear; \
|
echo "🎯 Доступные команды:"; \
|
||||||
echo "$(CYAN)🎯 Доступные команды:$(RESET)"; \
|
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo " $(BLUE)🔧 make role install$(RESET) - установить зависимости"; \
|
echo " 🔧 make role install - установить зависимости"; \
|
||||||
echo " $(BLUE)🔍 make role lint$(RESET) - проверить синтаксис ролей"; \
|
echo " 🔍 make role lint - проверить синтаксис ролей"; \
|
||||||
echo " $(PURPLE)🚀 make role test$(RESET) - протестировать роли (с default пресетом)"; \
|
echo " 🚀 make role test - протестировать роли (default preset)"; \
|
||||||
echo " $(PURPLE)🚀 make role test minimal$(RESET) - протестировать с minimal пресетом"; \
|
echo " 🚀 make role test [preset] - протестировать с любым preset"; \
|
||||||
echo " $(PURPLE)🚀 make role test standard$(RESET) - протестировать со standard пресетом"; \
|
echo " 🚀 make role test minimal - протестировать с minimal preset"; \
|
||||||
echo " $(PURPLE)🚀 make role test docker$(RESET) - протестировать с docker пресетом"; \
|
echo " 🚀 make role test standard - протестировать со standard preset"; \
|
||||||
echo " $(CYAN)📋 make role presets$(RESET) - показать список пресетов"; \
|
echo " 🚀 make role test docker - протестировать с docker preset"; \
|
||||||
echo " $(PURPLE)🚀 make role deploy$(RESET) - развернуть роли";; \
|
echo " 📋 make role presets - показать список preset'ов"; \
|
||||||
|
echo " 🚀 make role deploy - развернуть роли";; \
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Работа с Molecule Universal
|
# Работа с Molecule Universal
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
molecule:
|
molecule:
|
||||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
create) \
|
create) \
|
||||||
clear; \
|
|
||||||
echo "Создание тестового окружения ..."; \
|
echo "Создание тестового окружения ..."; \
|
||||||
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
bash -c "cd molecule/default && molecule create";; \
|
bash -c "cd molecule/default && molecule create";; \
|
||||||
converge) \
|
converge) \
|
||||||
clear; \
|
|
||||||
echo "Запуск плейбуков ..."; \
|
echo "Запуск плейбуков ..."; \
|
||||||
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
bash -c "cd molecule/default && molecule converge";; \
|
bash -c "cd molecule/default && molecule converge";; \
|
||||||
verify) \
|
verify) \
|
||||||
clear; \
|
|
||||||
echo "Проверка результатов ..."; \
|
echo "Проверка результатов ..."; \
|
||||||
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
bash -c "cd molecule/default && molecule verify";; \
|
bash -c "cd molecule/default && molecule verify";; \
|
||||||
destroy) \
|
destroy) \
|
||||||
clear; \
|
|
||||||
echo "Удаление тестового окружения ..."; \
|
echo "Удаление тестового окружения ..."; \
|
||||||
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
bash -c "cd molecule/default && molecule destroy";; \
|
bash -c "cd molecule/default && molecule destroy";; \
|
||||||
test) \
|
test) \
|
||||||
clear; \
|
|
||||||
echo "Полный цикл тестирования ..."; \
|
echo "Полный цикл тестирования ..."; \
|
||||||
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
bash -c "cd molecule/default && molecule test";; \
|
bash -c "cd molecule/default && molecule test";; \
|
||||||
*) \
|
*) \
|
||||||
clear; \
|
|
||||||
echo "Доступные команды:"; \
|
echo "Доступные команды:"; \
|
||||||
echo " make molecule create - создать окружение"; \
|
echo " make molecule create - создать окружение"; \
|
||||||
echo " make molecule converge - запустить плейбуки"; \
|
echo " make molecule converge - запустить плейбуки"; \
|
||||||
@@ -146,7 +171,6 @@ molecule:
|
|||||||
vault:
|
vault:
|
||||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
show) \
|
show) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -155,14 +179,12 @@ vault:
|
|||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault view --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault view --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
create) \
|
create) \
|
||||||
clear; \
|
echo "Создание файла секретов:"; \
|
||||||
echo "Создание файла секретов :"; \
|
|
||||||
read -p "Введите имя файла (без .yml): " FILE; \
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
|
docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
|
||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault create --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault create --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
edit) \
|
edit) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -171,14 +193,12 @@ vault:
|
|||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault edit --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault edit --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
delete) \
|
delete) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
read -p "Введите имя файла (без .yml): " FILE; \
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
rm -f vault/$$FILE.yml;; \
|
rm -f vault/$$FILE.yml;; \
|
||||||
rekey) \
|
rekey) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -187,7 +207,6 @@ vault:
|
|||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault rekey --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault rekey --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
decrypt) \
|
decrypt) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -196,7 +215,6 @@ vault:
|
|||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault decrypt --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault decrypt --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
encrypt) \
|
encrypt) \
|
||||||
clear; \
|
|
||||||
echo "Доступные файлы секретов:"; \
|
echo "Доступные файлы секретов:"; \
|
||||||
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
@@ -205,7 +223,6 @@ vault:
|
|||||||
quay.io/ansible/creator-ee:latest \
|
quay.io/ansible/creator-ee:latest \
|
||||||
ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
*) \
|
*) \
|
||||||
clear; \
|
|
||||||
echo "Доступные команды:"; \
|
echo "Доступные команды:"; \
|
||||||
echo " make vault create - создать файл секретов"; \
|
echo " make vault create - создать файл секретов"; \
|
||||||
echo " make vault edit - редактировать секреты"; \
|
echo " make vault edit - редактировать секреты"; \
|
||||||
@@ -238,18 +255,36 @@ git:
|
|||||||
git checkout -b $$NEW_BRANCH; \
|
git checkout -b $$NEW_BRANCH; \
|
||||||
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
|
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
|
||||||
*) \
|
*) \
|
||||||
clear; \
|
|
||||||
echo "Доступные команды:"; \
|
echo "Доступные команды:"; \
|
||||||
echo " make git push - запушить изменения"; \
|
echo " make git push - запушить изменения"; \
|
||||||
echo " make git pull - получить изменения"; \
|
echo " make git pull - получить изменения"; \
|
||||||
echo " make git new - создать новую ветку";; \
|
echo " make git new - создать новую ветку";; \
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с Docker (упрощенная)
|
||||||
|
####################################################################################################
|
||||||
|
docker:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
clean) \
|
||||||
|
echo "🧹 Очистка Docker ресурсов..."; \
|
||||||
|
docker system prune -f; \
|
||||||
|
docker volume prune -f; \
|
||||||
|
echo "✅ Docker ресурсы очищены";; \
|
||||||
|
*) \
|
||||||
|
echo "🐳 Docker команды:"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "make docker clean - очистить Docker ресурсы"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "💡 Основное тестирование через preset систему:"; \
|
||||||
|
echo " make role test [preset] - универсальное тестирование"; \
|
||||||
|
echo " make role presets - показать доступные preset'ы";; \
|
||||||
|
esac
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Справка
|
# Справка
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
help:
|
help:
|
||||||
@clear
|
|
||||||
@echo "=========================================="
|
@echo "=========================================="
|
||||||
@echo "AnsibleTemplate - Универсальная система"
|
@echo "AnsibleTemplate - Универсальная система"
|
||||||
@echo "тестирования Ansible ролей"
|
@echo "тестирования Ansible ролей"
|
||||||
@@ -263,21 +298,37 @@ help:
|
|||||||
@echo " vault/ - Зашифрованные секреты"
|
@echo " vault/ - Зашифрованные секреты"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "🚀 Основные команды:"
|
@echo "🚀 Основные команды:"
|
||||||
|
@echo ""
|
||||||
|
@echo "🧪 ТЕСТИРОВАНИЕ (Docker контейнеры):"
|
||||||
|
@echo " make role test - протестировать роли (default preset)"
|
||||||
|
@echo " make role test [preset] - протестировать с любым preset"
|
||||||
|
@echo " make role test minimal - тест с minimal preset"
|
||||||
|
@echo " make role test standard - тест со standard preset"
|
||||||
|
@echo " make role test docker - тест с docker preset"
|
||||||
|
@echo ""
|
||||||
|
@echo "🚀 РАЗВЕРТЫВАНИЕ (Реальные серверы):"
|
||||||
|
@echo " make role deploy - развернуть роли на серверы"
|
||||||
|
@echo ""
|
||||||
|
@echo "🔧 ВСПОМОГАТЕЛЬНЫЕ:"
|
||||||
@echo " make role install - установить зависимости"
|
@echo " make role install - установить зависимости"
|
||||||
@echo " make role lint - проверить синтаксис ролей"
|
@echo " make role lint - проверить синтаксис ролей"
|
||||||
@echo " make role test - протестировать роли"
|
@echo " make docker clean - очистить Docker ресурсы"
|
||||||
@echo " make role deploy - развернуть роли на серверы"
|
|
||||||
@echo " make molecule create - создать тестовое окружение"
|
|
||||||
@echo " make vault create - создать файл секретов"
|
@echo " make vault create - создать файл секретов"
|
||||||
@echo " make git new - создать новую ветку"
|
@echo " make git new - создать новую ветку"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "📖 Для подробной справки:"
|
@echo "📖 Для подробной справки:"
|
||||||
@echo " make role - команды для ролей"
|
@echo " make role - команды для ролей"
|
||||||
@echo " make molecule - команды Molecule"
|
@echo " make molecule - команды Molecule"
|
||||||
|
@echo " make docker - команды Docker"
|
||||||
@echo " make vault - команды Vault"
|
@echo " make vault - команды Vault"
|
||||||
@echo " make git - команды Git"
|
@echo " make git - команды Git"
|
||||||
@echo "=========================================="
|
@echo "=========================================="
|
||||||
|
|
||||||
# Пустые цели для совместимости
|
# Пустые цели для совместимости
|
||||||
view create edit show delete test lint deploy new advanced presets:
|
view create edit show delete lint deploy new advanced presets:
|
||||||
|
@true
|
||||||
|
|
||||||
|
# Динамические цели для всех возможных preset'ов
|
||||||
|
# Это позволяет использовать make role test [любой_preset] без ошибок
|
||||||
|
%:
|
||||||
@true
|
@true
|
||||||
347
Makefile.backup
Normal file
347
Makefile.backup
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# AnsibleTemplate - Универсальная система тестирования Ansible ролей
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ЦВЕТА ДЛЯ ВЫВОДА
|
||||||
|
# =============================================================================
|
||||||
|
RED := \033[0;31m
|
||||||
|
GREEN := \033[0;32m
|
||||||
|
YELLOW := \033[0;33m
|
||||||
|
BLUE := \033[0;34m
|
||||||
|
PURPLE := \033[0;35m
|
||||||
|
CYAN := \033[0;36m
|
||||||
|
WHITE := \033[0;37m
|
||||||
|
RESET := \033[0m
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
|
||||||
|
# =============================================================================
|
||||||
|
PROJECT_NAME ?= ansible-template
|
||||||
|
VERSION ?= 0.1.0
|
||||||
|
AUTHOR ?= "Сергей Антропов"
|
||||||
|
SITE ?= "https://devops.org.ru"
|
||||||
|
DOCKER_IMAGE ?= quay.io/ansible/creator-ee:latest
|
||||||
|
CONTAINER_NAME ?= ansible-controller
|
||||||
|
|
||||||
|
.PHONY: role molecule vault git docker help
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с ролями
|
||||||
|
####################################################################################################
|
||||||
|
role:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
lint) \
|
||||||
|
clear; \
|
||||||
|
echo "$(BLUE)🔍 Проверка синтаксиса ролей ...$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace -e ANSIBLE_FORCE_COLOR=1 $(DOCKER_IMAGE) bash -c "ansible-lint roles/ --config-file .ansible-lint || true"; \
|
||||||
|
echo "$(GREEN)✅ Lint завершен$(RESET)";; \
|
||||||
|
test) \
|
||||||
|
clear; \
|
||||||
|
echo "$(PURPLE)🚀 Тестирование ролей ...$(RESET)"; \
|
||||||
|
PRESET="default"; \
|
||||||
|
# Получаем все аргументы после 'test' и берем первый как preset \
|
||||||
|
ARGS="$(filter-out test,$(MAKECMDGOALS))"; \
|
||||||
|
if [ -n "$$ARGS" ]; then \
|
||||||
|
PRESET="$$(echo $$ARGS | cut -d' ' -f1)"; \
|
||||||
|
fi; \
|
||||||
|
echo "$(CYAN)📋 Используется пресет: $(YELLOW)$$PRESET$(RESET)"; \
|
||||||
|
if [ ! -f "molecule/presets/$$PRESET.yml" ]; then \
|
||||||
|
echo "$(RED)❌ Ошибка: Пресет '$$PRESET' не найден!$(RESET)"; \
|
||||||
|
echo "$(YELLOW)💡 Доступные пресеты:$(RESET)"; \
|
||||||
|
ls -1 molecule/presets/*.yml 2>/dev/null | sed 's|molecule/presets/||g' | sed 's|\.yml||g' | sed 's/^/ - /' || echo " $(YELLOW)⚠️ Пресеты не найдены$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(GREEN)💡 Использование:$(RESET)"; \
|
||||||
|
echo " $(BLUE)make role test$(RESET) - с default preset"; \
|
||||||
|
echo " $(BLUE)make role test [preset_name]$(RESET) - с любым preset"; \
|
||||||
|
echo " $(BLUE)make role test minimal$(RESET) - с minimal preset"; \
|
||||||
|
echo " $(BLUE)make role test standard$(RESET) - со standard preset"; \
|
||||||
|
echo " $(BLUE)make role test docker$(RESET) - с docker preset"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
echo ""; \
|
||||||
|
docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
-e ANSIBLE_FORCE_COLOR=1 \
|
||||||
|
-e MOLECULE_PRESET=$$PRESET \
|
||||||
|
$(DOCKER_IMAGE) \
|
||||||
|
bash -c "cd molecule/default && ansible-playbook -i localhost, site.yml --connection=local" || echo "$(GREEN)✅ Тестирование завершено$(RESET)";; \
|
||||||
|
presets) \
|
||||||
|
clear; \
|
||||||
|
echo "$(CYAN)📋 Доступные пресеты:$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
preset_count=0; \
|
||||||
|
for preset in molecule/presets/*.yml; do \
|
||||||
|
if [ -f "$$preset" ]; then \
|
||||||
|
preset_name=$$(basename "$$preset" .yml); \
|
||||||
|
preset_desc=$$(grep -E "^#.*пресет|^#.*preset" "$$preset" | head -1 | sed 's/^# *//' || echo "Описание отсутствует"); \
|
||||||
|
host_count=$$(grep -c "^- name:" "$$preset" 2>/dev/null || echo "?"); \
|
||||||
|
printf " $(BLUE)📄 %s$(RESET) - %s $(GREEN)(%s хостов)$(RESET)\n" "$$preset_name" "$$preset_desc" "$$host_count"; \
|
||||||
|
preset_count=$$((preset_count + 1)); \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
if [ $$preset_count -eq 0 ]; then \
|
||||||
|
echo " $(YELLOW)⚠️ Пресеты не найдены$(RESET)"; \
|
||||||
|
fi; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(GREEN)💡 Использование:$(RESET)"; \
|
||||||
|
echo " $(BLUE)make role test$(RESET) - с default preset"; \
|
||||||
|
echo " $(BLUE)make role test [preset_name]$(RESET) - с любым preset"; \
|
||||||
|
echo " $(BLUE)make role test minimal$(RESET) - с minimal preset"; \
|
||||||
|
echo " $(BLUE)make role test standard$(RESET) - со standard preset"; \
|
||||||
|
echo " $(BLUE)make role test docker$(RESET) - с docker preset"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(YELLOW)💡 Примеры:$(RESET)"; \
|
||||||
|
echo " $(BLUE)make role test$(RESET) # default preset"; \
|
||||||
|
echo " $(BLUE)make role test minimal$(RESET) # minimal preset"; \
|
||||||
|
echo " $(BLUE)make role test my-custom-preset$(RESET) # любой preset";; \
|
||||||
|
deploy) \
|
||||||
|
clear; \
|
||||||
|
echo "$(PURPLE)🚀 Развертывание ролей на реальные серверы...$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(YELLOW)💡 Примеры использования:$(RESET)"; \
|
||||||
|
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml$(RESET)"; \
|
||||||
|
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml --limit web_servers$(RESET)"; \
|
||||||
|
echo " $(GREEN)ansible-playbook -i inventory/hosts.ini deploy.yml --check$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(CYAN)📄 Доступные playbook:$(RESET)"; \
|
||||||
|
ls -la *.yml 2>/dev/null | grep -v molecule || echo " $(BLUE)📄 deploy.yml - основной playbook для развертывания$(RESET)";; \
|
||||||
|
*) \
|
||||||
|
clear; \
|
||||||
|
echo "$(CYAN)🎯 Доступные команды:$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
echo " $(BLUE)🔧 make role install$(RESET) - установить зависимости"; \
|
||||||
|
echo " $(BLUE)🔍 make role lint$(RESET) - проверить синтаксис ролей"; \
|
||||||
|
echo " $(PURPLE)🚀 make role test$(RESET) - протестировать роли (default preset)"; \
|
||||||
|
echo " $(PURPLE)🚀 make role test [preset]$(RESET) - протестировать с любым preset"; \
|
||||||
|
echo " $(PURPLE)🚀 make role test minimal$(RESET) - протестировать с minimal preset"; \
|
||||||
|
echo " $(PURPLE)🚀 make role test standard$(RESET) - протестировать со standard preset"; \
|
||||||
|
echo " $(PURPLE)🚀 make role test docker$(RESET) - протестировать с docker preset"; \
|
||||||
|
echo " $(CYAN)📋 make role presets$(RESET) - показать список preset'ов"; \
|
||||||
|
echo " $(PURPLE)🚀 make role deploy$(RESET) - развернуть роли";; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с Molecule Universal
|
||||||
|
####################################################################################################
|
||||||
|
molecule:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
create) \
|
||||||
|
clear; \
|
||||||
|
echo "Создание тестового окружения ..."; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
bash -c "cd molecule/default && molecule create";; \
|
||||||
|
converge) \
|
||||||
|
clear; \
|
||||||
|
echo "Запуск плейбуков ..."; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
bash -c "cd molecule/default && molecule converge";; \
|
||||||
|
verify) \
|
||||||
|
clear; \
|
||||||
|
echo "Проверка результатов ..."; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
bash -c "cd molecule/default && molecule verify";; \
|
||||||
|
destroy) \
|
||||||
|
clear; \
|
||||||
|
echo "Удаление тестового окружения ..."; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
bash -c "cd molecule/default && molecule destroy";; \
|
||||||
|
test) \
|
||||||
|
clear; \
|
||||||
|
echo "Полный цикл тестирования ..."; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
bash -c "cd molecule/default && molecule test";; \
|
||||||
|
*) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные команды:"; \
|
||||||
|
echo " make molecule create - создать окружение"; \
|
||||||
|
echo " make molecule converge - запустить плейбуки"; \
|
||||||
|
echo " make molecule verify - проверить результаты"; \
|
||||||
|
echo " make molecule destroy - удалить окружение"; \
|
||||||
|
echo " make molecule test - полный цикл тестирования"; \
|
||||||
|
;; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с Ansible Vault
|
||||||
|
####################################################################################################
|
||||||
|
vault:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
show) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault view --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
create) \
|
||||||
|
clear; \
|
||||||
|
echo "Создание файла секретов :"; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault create --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
edit) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault edit --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
delete) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
rm -f vault/$$FILE.yml;; \
|
||||||
|
rekey) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault rekey --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
decrypt) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault decrypt --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
encrypt) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные файлы секретов:"; \
|
||||||
|
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
|
||||||
|
echo ""; \
|
||||||
|
read -p "Введите имя файла (без .yml): " FILE; \
|
||||||
|
docker run --rm -v "$(PWD):/workspace" -w /workspace \
|
||||||
|
quay.io/ansible/creator-ee:latest \
|
||||||
|
ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
|
||||||
|
*) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные команды:"; \
|
||||||
|
echo " make vault create - создать файл секретов"; \
|
||||||
|
echo " make vault edit - редактировать секреты"; \
|
||||||
|
echo " make vault show - показать секреты"; \
|
||||||
|
echo " make vault delete - удалить секреты"; \
|
||||||
|
echo " make vault encrypt - зашифровать файл"; \
|
||||||
|
echo " make vault decrypt - расшифровать файл"; \
|
||||||
|
echo " make vault rekey - сменить пароль";; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с Git
|
||||||
|
####################################################################################################
|
||||||
|
git:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
push) \
|
||||||
|
git branch; \
|
||||||
|
read -p "Выберите ветку для пуша: " BRANCH; \
|
||||||
|
read -p "Введите описание коммита: " COMMIT; \
|
||||||
|
commitname=$$COMMIT; \
|
||||||
|
git add . ; \
|
||||||
|
git commit -m "$$commitname"; \
|
||||||
|
git push -u origin $$BRANCH; \
|
||||||
|
echo "Изменения внесены в Git";; \
|
||||||
|
pull) \
|
||||||
|
git pull;; \
|
||||||
|
new) \
|
||||||
|
read -p "Введите имя новой ветки: " BRANCH_NAME; \
|
||||||
|
NEW_BRANCH="$$BRANCH_NAME"; \
|
||||||
|
git checkout -b $$NEW_BRANCH; \
|
||||||
|
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
|
||||||
|
*) \
|
||||||
|
clear; \
|
||||||
|
echo "Доступные команды:"; \
|
||||||
|
echo " make git push - запушить изменения"; \
|
||||||
|
echo " make git pull - получить изменения"; \
|
||||||
|
echo " make git new - создать новую ветку";; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Работа с Docker (упрощенная)
|
||||||
|
####################################################################################################
|
||||||
|
docker:
|
||||||
|
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||||
|
clean) \
|
||||||
|
clear; \
|
||||||
|
echo "$(RED)🧹 Очистка Docker ресурсов...$(RESET)"; \
|
||||||
|
docker system prune -f; \
|
||||||
|
docker volume prune -f; \
|
||||||
|
echo "$(GREEN)✅ Docker ресурсы очищены$(RESET)";; \
|
||||||
|
*) \
|
||||||
|
clear; \
|
||||||
|
echo "$(CYAN)🐳 Docker команды:$(RESET)"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(RED)make docker clean$(RESET) - очистить Docker ресурсы"; \
|
||||||
|
echo ""; \
|
||||||
|
echo "$(YELLOW)💡 Основное тестирование через preset систему:$(RESET)"; \
|
||||||
|
echo " $(BLUE)make role test [preset]$(RESET) - универсальное тестирование"; \
|
||||||
|
echo " $(BLUE)make role presets$(RESET) - показать доступные preset'ы";; \
|
||||||
|
esac
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# Справка
|
||||||
|
####################################################################################################
|
||||||
|
help:
|
||||||
|
@clear
|
||||||
|
@echo "=========================================="
|
||||||
|
@echo "AnsibleTemplate - Универсальная система"
|
||||||
|
@echo "тестирования Ansible ролей"
|
||||||
|
@echo "=========================================="
|
||||||
|
@echo ""
|
||||||
|
@echo "📁 Структура проекта:"
|
||||||
|
@echo " scripts/ - Скрипты автоматизации"
|
||||||
|
@echo " inventory/ - Инвентори файлы"
|
||||||
|
@echo " molecule/default/ - Molecule конфигурация"
|
||||||
|
@echo " roles/ - Ansible роли"
|
||||||
|
@echo " vault/ - Зашифрованные секреты"
|
||||||
|
@echo ""
|
||||||
|
@echo "🚀 Основные команды:"
|
||||||
|
@echo " make role install - установить зависимости"
|
||||||
|
@echo " make role lint - проверить синтаксис ролей"
|
||||||
|
@echo " make role test - протестировать роли (default preset)"
|
||||||
|
@echo " make role test [preset] - протестировать с любым preset"
|
||||||
|
@echo " make role test minimal - тест с minimal preset"
|
||||||
|
@echo " make role test standard - тест со standard preset"
|
||||||
|
@echo " make role test docker - тест с docker preset"
|
||||||
|
@echo " make role deploy - развернуть роли на серверы"
|
||||||
|
@echo " make docker clean - очистить Docker ресурсы"
|
||||||
|
@echo " make vault create - создать файл секретов"
|
||||||
|
@echo " make git new - создать новую ветку"
|
||||||
|
@echo ""
|
||||||
|
@echo "📖 Для подробной справки:"
|
||||||
|
@echo " make role - команды для ролей"
|
||||||
|
@echo " make molecule - команды Molecule"
|
||||||
|
@echo " make docker - команды Docker"
|
||||||
|
@echo " make vault - команды Vault"
|
||||||
|
@echo " make git - команды Git"
|
||||||
|
@echo "=========================================="
|
||||||
|
|
||||||
|
# Пустые цели для совместимости
|
||||||
|
view create edit show delete test lint deploy new advanced presets:
|
||||||
|
@true
|
||||||
|
|
||||||
|
# Динамические цели для всех возможных preset'ов
|
||||||
|
# Это позволяет использовать make role test [любой_preset] без ошибок
|
||||||
|
%:
|
||||||
|
@true
|
||||||
224
README.md
Normal file
224
README.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# AnsibleTemplate - Универсальная система тестирования Ansible ролей
|
||||||
|
|
||||||
|
**Автор:** Сергей Антропов
|
||||||
|
**Сайт:** https://devops.org.ru
|
||||||
|
|
||||||
|
## 🚀 Описание
|
||||||
|
|
||||||
|
AnsibleTemplate - это универсальная система для тестирования Ansible ролей с использованием Docker и различных preset'ов конфигурации.
|
||||||
|
|
||||||
|
## 🔧 Исправленные проблемы
|
||||||
|
|
||||||
|
### 1. **Проблема с preset'ами в Makefile**
|
||||||
|
- ✅ **Исправлено**: Полностью универсальная система - любой preset без изменения Makefile
|
||||||
|
- ✅ **Добавлено**: Динамическое определение preset'ов через `filter-out` и `cut`
|
||||||
|
- ✅ **Улучшено**: Динамическая загрузка preset'ов через `include_vars` в Ansible
|
||||||
|
- ✅ **Добавлено**: Fallback значения и подробная справка при ошибках
|
||||||
|
|
||||||
|
### 2. **Дублирование файлов preset'ов**
|
||||||
|
- ✅ **Исправлено**: Создан уникальный `default.yml` preset с 2 хостами (Debian + RHEL)
|
||||||
|
- ✅ **Сохранено**: `minimal.yml` для быстрого тестирования с 1 хостом
|
||||||
|
- ✅ **Улучшено**: Различные конфигурации для разных сценариев тестирования
|
||||||
|
|
||||||
|
### 3. **Проблемы с именами контейнеров**
|
||||||
|
- ✅ **Исправлено**: Унифицированы имена контейнеров (`ansible-controller`)
|
||||||
|
- ✅ **Обновлено**: Все файлы теперь используют одинаковые имена контейнеров
|
||||||
|
|
||||||
|
### 4. **Проблемы с путями**
|
||||||
|
- ✅ **Исправлено**: Пути в `create.yml` и `destroy.yml` теперь используют `preset.yml`
|
||||||
|
- ✅ **Добавлено**: Fallback значения для случаев отсутствия preset файлов
|
||||||
|
- ✅ **Улучшено**: Более надежная работа с переменными
|
||||||
|
|
||||||
|
### 5. **Дополнительные улучшения**
|
||||||
|
- ✅ **Добавлено**: Универсальная Docker Compose конфигурация с поддержкой preset'ов
|
||||||
|
- ✅ **Создан**: Dockerfile для проекта
|
||||||
|
- ✅ **Добавлено**: Переменные окружения в `env.example`
|
||||||
|
- ✅ **Улучшено**: Расширенная справка и команды Docker
|
||||||
|
- ✅ **Добавлено**: Команды для работы с preset'ами через Docker Compose
|
||||||
|
|
||||||
|
## 📁 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
AnsibleTemplate/
|
||||||
|
├── molecule/
|
||||||
|
│ ├── default/ # Molecule конфигурация
|
||||||
|
│ │ ├── create.yml # Создание тестовых контейнеров
|
||||||
|
│ │ ├── converge.yml # Запуск тестов
|
||||||
|
│ │ ├── destroy.yml # Удаление контейнеров
|
||||||
|
│ │ └── site.yml # Основной playbook
|
||||||
|
│ └── presets/ # Preset'ы конфигурации
|
||||||
|
│ ├── default.yml # Стандартный preset (2 хоста)
|
||||||
|
│ ├── minimal.yml # Минимальный preset (1 хост)
|
||||||
|
│ ├── standard.yml # Расширенный preset (3 хоста)
|
||||||
|
│ └── docker.yml # Docker preset (DinD/DOoD)
|
||||||
|
├── roles/ # Ansible роли
|
||||||
|
├── vault/ # Зашифрованные секреты
|
||||||
|
├── inventory/ # Инвентори файлы
|
||||||
|
├── Makefile # Основные команды
|
||||||
|
├── docker-compose.yml # Docker Compose конфигурация
|
||||||
|
├── Dockerfile # Docker образ
|
||||||
|
└── env.example # Переменные окружения
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Использование
|
||||||
|
|
||||||
|
### 🧪 Тестирование (Docker контейнеры)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Просмотр доступных preset'ов
|
||||||
|
make role presets
|
||||||
|
|
||||||
|
# Тестирование с разными preset'ами
|
||||||
|
make role test # default preset
|
||||||
|
make role test minimal # minimal preset
|
||||||
|
make role test standard # standard preset
|
||||||
|
make role test docker # docker preset
|
||||||
|
make role test performance # performance preset
|
||||||
|
make role test security # security preset
|
||||||
|
make role test my-custom-preset # любой custom preset
|
||||||
|
```
|
||||||
|
|
||||||
|
**Особенности тестирования:**
|
||||||
|
- Использует Docker контейнеры для изоляции
|
||||||
|
- Динамический inventory создается из preset файлов
|
||||||
|
- Временные контейнеры (u1, u2, u3, ...)
|
||||||
|
- Автоматическая очистка после тестов
|
||||||
|
|
||||||
|
### 🚀 Развертывание (Реальные серверы)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Развертывание на реальные серверы
|
||||||
|
make role deploy # развертывание ролей
|
||||||
|
```
|
||||||
|
|
||||||
|
**Особенности развертывания:**
|
||||||
|
- Использует статический `inventory/hosts.ini`
|
||||||
|
- Подключение по SSH к реальным серверам
|
||||||
|
- Dry-run проверка перед развертыванием
|
||||||
|
- Подтверждение пользователя
|
||||||
|
|
||||||
|
### 🔧 Вспомогательные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker команды
|
||||||
|
make docker clean # очистить Docker ресурсы
|
||||||
|
|
||||||
|
# Другие команды
|
||||||
|
make role lint # проверка синтаксиса
|
||||||
|
make vault create # создание секретов
|
||||||
|
make help # полная справка
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preset'ы конфигурации
|
||||||
|
|
||||||
|
| Preset | Описание | Хосты | Использование |
|
||||||
|
|--------|----------|-------|---------------|
|
||||||
|
| `default` | Стандартный preset | 2 хоста (Debian + RHEL) | Базовое тестирование |
|
||||||
|
| `minimal` | Минимальный preset | 1 хост (Debian) | Быстрое тестирование |
|
||||||
|
| `standard` | Расширенный preset | 3 хоста (Debian + RHEL + Debian) | Полное тестирование |
|
||||||
|
| `docker` | Docker preset | DinD + DOoD узлы | Тестирование Docker функциональности |
|
||||||
|
| `performance` | Performance preset | 5 хостов (Debian + RHEL) | Нагрузочное тестирование |
|
||||||
|
| `security` | Security preset | 3 хоста (Debian + RHEL) | Тестирование безопасности |
|
||||||
|
| `[custom]` | Любой custom preset | Любое количество | Пользовательские сценарии |
|
||||||
|
|
||||||
|
## 🔧 Технические детали
|
||||||
|
|
||||||
|
### Исправления в Makefile
|
||||||
|
|
||||||
|
1. **Универсальное определение preset'а**:
|
||||||
|
```bash
|
||||||
|
ARGS="$(filter-out test,$(MAKECMDGOALS))"
|
||||||
|
PRESET="$$(echo $$ARGS | cut -d' ' -f1)"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка существования preset'ов с подробной справкой**:
|
||||||
|
```bash
|
||||||
|
if [ ! -f "molecule/presets/$$PRESET.yml" ]; then
|
||||||
|
echo "❌ Ошибка: Пресет '$PRESET' не найден!"
|
||||||
|
# Показать доступные preset'ы и примеры использования
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Динамическая загрузка в Ansible**:
|
||||||
|
```yaml
|
||||||
|
- name: Load preset configuration
|
||||||
|
include_vars: "{{ preset_file }}"
|
||||||
|
when: preset_file is file
|
||||||
|
ignore_errors: true
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Поддержка любых preset'ов без изменения Makefile**:
|
||||||
|
```makefile
|
||||||
|
# Динамические цели для всех возможных preset'ов
|
||||||
|
%:
|
||||||
|
@true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fallback значения
|
||||||
|
|
||||||
|
Добавлены fallback значения в `create.yml` и `destroy.yml` для случаев отсутствия preset файлов:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vars:
|
||||||
|
# Fallback значения если preset.yml не найден
|
||||||
|
docker_network: labnet
|
||||||
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
|
# ... остальные значения
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐳 Docker поддержка
|
||||||
|
|
||||||
|
### Упрощенная архитектура
|
||||||
|
|
||||||
|
Docker-compose удален из проекта, так как он избыточен при наличии универсальной системы preset'ов. Все тестирование происходит через preset систему:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Основное тестирование через preset систему
|
||||||
|
make role test [любой_preset] # универсальное тестирование
|
||||||
|
make role presets # показать доступные preset'ы
|
||||||
|
|
||||||
|
# Очистка Docker ресурсов
|
||||||
|
make docker clean # очистить Docker ресурсы
|
||||||
|
```
|
||||||
|
|
||||||
|
**Преимущества упрощенной архитектуры:**
|
||||||
|
- ✅ **Простота**: Один способ тестирования через preset систему
|
||||||
|
- ✅ **Универсальность**: Любой preset без дополнительной конфигурации
|
||||||
|
- ✅ **Автономность**: Preset система сама управляет контейнерами
|
||||||
|
- ✅ **Меньше сложности**: Нет дублирования функциональности
|
||||||
|
|
||||||
|
## 📝 Переменные окружения
|
||||||
|
|
||||||
|
Скопируйте `env.example` в `.env` и настройте под свои нужды:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Результат
|
||||||
|
|
||||||
|
Теперь система тестирования работает корректно:
|
||||||
|
|
||||||
|
1. ✅ **Полностью универсальная система preset'ов - любой preset без изменения Makefile**
|
||||||
|
2. ✅ **Динамическое определение preset'ов через `filter-out` и `cut`**
|
||||||
|
3. ✅ **Проверка существования preset'ов с подробной справкой**
|
||||||
|
4. ✅ **Унифицированные имена контейнеров**
|
||||||
|
5. ✅ **Надежные fallback значения**
|
||||||
|
6. ✅ **Упрощенная архитектура без docker-compose**
|
||||||
|
7. ✅ **Поддержка неограниченного количества custom preset'ов**
|
||||||
|
8. ✅ **Автономная preset система сама управляет контейнерами**
|
||||||
|
9. ✅ **Подробная документация и справка**
|
||||||
|
|
||||||
|
## 📞 Поддержка
|
||||||
|
|
||||||
|
При возникновении проблем:
|
||||||
|
|
||||||
|
1. Проверьте наличие Docker и Docker Compose
|
||||||
|
2. Убедитесь, что все preset файлы существуют
|
||||||
|
3. Используйте `make help` для справки
|
||||||
|
4. Проверьте логи: `make docker shell` и `docker logs ansible-controller`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Автор:** Сергей Антропов
|
||||||
|
**Сайт:** https://devops.org.ru
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
inventory = inventory/hosts.ini
|
inventory = inventory/hosts.ini
|
||||||
vault_password_file = vault/.vault
|
# vault_password_file = vault/.vault
|
||||||
remote_user = devops
|
remote_user = devops
|
||||||
host_key_checking = False
|
host_key_checking = False
|
||||||
enable_plugins = yaml, ini
|
enable_plugins = yaml, ini
|
||||||
|
|||||||
13
deploy.yml
13
deploy.yml
@@ -1,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
# Playbook для развертывания ролей на реальные серверы
|
# Плейбук для развертывания ролей
|
||||||
# Автор: Сергей Антропов
|
# Автор: Сергей Антропов
|
||||||
# Сайт: https://devops.org.ru
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
- name: Deploy nginx on production servers
|
- name: Test nginx role
|
||||||
hosts: all
|
hosts: all
|
||||||
become: true
|
become: true
|
||||||
roles:
|
roles:
|
||||||
- role: nginx
|
- nginx
|
||||||
vars:
|
tags:
|
||||||
nginx_server_name: "{{ ansible_fqdn | default(ansible_hostname) }}"
|
- nginx
|
||||||
nginx_listen_port: 80
|
- test
|
||||||
nginx_root_dir: "/var/www/html"
|
|
||||||
|
|||||||
99
docs/testing-vs-deployment.md
Normal file
99
docs/testing-vs-deployment.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Тестирование vs Развертывание
|
||||||
|
|
||||||
|
## Автор: Сергей Антропов
|
||||||
|
## Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
## Различие между тестированием и развертыванием
|
||||||
|
|
||||||
|
### 🧪 Тестирование (molecule)
|
||||||
|
|
||||||
|
**Команда:** `make role test [preset]`
|
||||||
|
|
||||||
|
**Использует:**
|
||||||
|
- Динамический inventory, создаваемый в `molecule/default/create.yml`
|
||||||
|
- Preset файлы из `molecule/presets/`
|
||||||
|
- Docker контейнеры для изоляции тестов
|
||||||
|
- Временные контейнеры (u1, u2, u3)
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. Загружается preset конфигурация
|
||||||
|
2. Создаются Docker контейнеры согласно preset
|
||||||
|
3. Генерируется временный inventory файл
|
||||||
|
4. Запускаются тесты на контейнерах
|
||||||
|
5. Контейнеры удаляются после тестов
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```bash
|
||||||
|
make role test standart # Тестирование в 3 контейнерах
|
||||||
|
make role test minimal # Тестирование в 1 контейнере
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 Развертывание (deploy)
|
||||||
|
|
||||||
|
**Команда:** `make role deploy`
|
||||||
|
|
||||||
|
**Использует:**
|
||||||
|
- Статический inventory файл `inventory/hosts.ini`
|
||||||
|
- Реальные серверы
|
||||||
|
- SSH подключение
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. Проверяется наличие `inventory/hosts.ini`
|
||||||
|
2. Показывается содержимое inventory
|
||||||
|
3. Запускается dry-run (--check)
|
||||||
|
4. Запрашивается подтверждение
|
||||||
|
5. Выполняется развертывание на реальные серверы
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```bash
|
||||||
|
make role deploy # Развертывание на реальные серверы
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
### Для тестирования
|
||||||
|
- Preset файлы: `molecule/presets/*.yml`
|
||||||
|
- Динамический inventory создается в `create.yml`
|
||||||
|
- Контейнеры: Docker
|
||||||
|
|
||||||
|
### Для развертывания
|
||||||
|
- Inventory файл: `inventory/hosts.ini`
|
||||||
|
- Серверы: Реальные хосты
|
||||||
|
- Подключение: SSH
|
||||||
|
|
||||||
|
## Примеры конфигурации
|
||||||
|
|
||||||
|
### Preset для тестирования (molecule/presets/standart.yml)
|
||||||
|
```yaml
|
||||||
|
hosts:
|
||||||
|
- name: u1
|
||||||
|
family: debian
|
||||||
|
groups: [test]
|
||||||
|
- name: u2
|
||||||
|
family: rhel
|
||||||
|
groups: [test]
|
||||||
|
- name: u3
|
||||||
|
family: debian
|
||||||
|
groups: [test]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inventory для развертывания (inventory/hosts.ini)
|
||||||
|
```ini
|
||||||
|
[web_servers]
|
||||||
|
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
|
||||||
|
web2 ansible_host=192.168.1.11 ansible_user=ubuntu
|
||||||
|
|
||||||
|
[db_servers]
|
||||||
|
db1 ansible_host=192.168.1.20 ansible_user=ubuntu
|
||||||
|
|
||||||
|
[all:vars]
|
||||||
|
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||||
|
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Рекомендации
|
||||||
|
|
||||||
|
1. **Сначала тестируйте** с помощью `make role test [preset]`
|
||||||
|
2. **Затем развертывайте** с помощью `make role deploy`
|
||||||
|
3. **Используйте разные preset'ы** для разных сценариев тестирования
|
||||||
|
4. **Настройте inventory** для ваших реальных серверов
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
# Автоматически сгенерированный инвентори
|
# Инвентори для развертывания на реальные серверы
|
||||||
# Автор: Сергей Антропов
|
# Автор: Сергей Антропов
|
||||||
# Сайт: https://devops.org.ru
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
[test]
|
# Примеры серверов (замените на ваши реальные серверы)
|
||||||
u1
|
[web_servers]
|
||||||
|
# web1 ansible_host=192.168.1.10 ansible_user=ubuntu
|
||||||
|
# web2 ansible_host=192.168.1.11 ansible_user=ubuntu
|
||||||
|
|
||||||
|
[db_servers]
|
||||||
|
# db1 ansible_host=192.168.1.20 ansible_user=ubuntu
|
||||||
|
|
||||||
|
[all:vars]
|
||||||
|
# Общие переменные для всех серверов
|
||||||
|
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||||
|
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- name: Install collections
|
- name: Install collections
|
||||||
community.docker.docker_container_exec:
|
community.docker.docker_container_exec:
|
||||||
container: ansible
|
container: ansible-controller
|
||||||
command: bash -lc "ansible-galaxy collection install -r /ansible/requirements.yml --force --no-deps --upgrade >/dev/null 2>&1 || true"
|
command: bash -lc "ansible-galaxy collection install -r /ansible/requirements.yml --force --no-deps --upgrade >/dev/null 2>&1 || true"
|
||||||
|
|
||||||
- name: Decrypt vault targets (best-effort)
|
- name: Decrypt vault targets (best-effort)
|
||||||
community.docker.docker_container_exec:
|
community.docker.docker_container_exec:
|
||||||
container: ansible
|
container: ansible-controller
|
||||||
command: >
|
command: >
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail;
|
set -euo pipefail;
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
- name: Run external playbook (your lab play)
|
- name: Run external playbook (your lab play)
|
||||||
community.docker.docker_container_exec:
|
community.docker.docker_container_exec:
|
||||||
container: ansible
|
container: ansible-controller
|
||||||
command: >
|
command: >
|
||||||
bash -lc "
|
bash -lc "
|
||||||
ANSIBLE_ROLES_PATH=/ansible/roles
|
ANSIBLE_ROLES_PATH=/ansible/roles
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
- name: Re-encrypt vault targets (always)
|
- name: Re-encrypt vault targets (always)
|
||||||
community.docker.docker_container_exec:
|
community.docker.docker_container_exec:
|
||||||
container: ansible
|
container: ansible-controller
|
||||||
command: >
|
command: >
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail;
|
set -euo pipefail;
|
||||||
|
|||||||
@@ -1,70 +1,88 @@
|
|||||||
---
|
---
|
||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars_files:
|
vars:
|
||||||
- ../presets/default.yml
|
# Получаем preset из переменной окружения или используем default
|
||||||
|
preset_name: "{{ lookup('env', 'MOLECULE_PRESET') | default('default') }}"
|
||||||
|
preset_file: "/workspace/molecule/presets/{{ preset_name }}.yml"
|
||||||
|
|
||||||
|
# Fallback значения если preset файл не найден
|
||||||
|
docker_network: labnet
|
||||||
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
|
images:
|
||||||
|
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
||||||
|
systemd_defaults:
|
||||||
|
privileged: true
|
||||||
|
command: "/sbin/init"
|
||||||
|
volumes:
|
||||||
|
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||||
|
tmpfs: ["/run", "/run/lock"]
|
||||||
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
hosts:
|
||||||
|
- name: u1
|
||||||
|
family: debian
|
||||||
|
groups: [test]
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: Load preset configuration
|
||||||
|
include_vars: "{{ preset_file }}"
|
||||||
|
when: preset_file is file
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Ensure network exists
|
- name: Ensure network exists
|
||||||
community.docker.docker_network:
|
command: docker network create {{ docker_network }}
|
||||||
name: "{{ docker_network }}"
|
delegate_to: localhost
|
||||||
state: present
|
ignore_errors: true
|
||||||
|
|
||||||
# SYSTEMD nodes
|
# SYSTEMD nodes
|
||||||
- name: Pull systemd images
|
- name: Pull systemd images
|
||||||
community.docker.docker_image:
|
command: docker pull {{ images[item.family] }}
|
||||||
name: "{{ images[item.family] }}"
|
delegate_to: localhost
|
||||||
source: pull
|
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
|
|
||||||
- name: Start systemd nodes
|
- name: Start systemd nodes
|
||||||
community.docker.docker_container:
|
command: >
|
||||||
name: "{{ item.name }}"
|
docker run -d --name {{ item.name }}
|
||||||
image: "{{ images[item.family] }}"
|
--network {{ docker_network }}
|
||||||
networks: [ { name: "{{ docker_network }}" } ]
|
--privileged={{ systemd_defaults.privileged | lower }}
|
||||||
privileged: "{{ systemd_defaults.privileged }}"
|
--tmpfs {{ (systemd_defaults.tmpfs | default([])) | join(' --tmpfs ') }}
|
||||||
command: "{{ systemd_defaults.command }}"
|
--cap-add {{ (systemd_defaults.capabilities | default([])) | join(' --cap-add ') }}
|
||||||
volumes: "{{ (systemd_defaults.volumes | default([])) + (item.volumes | default([])) }}"
|
{% for port in item.publish | default([]) %}--publish {{ port }} {% endfor %}
|
||||||
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
|
{% for key, value in item.env | default({}) | dictsort %}--env {{ key }}={{ value }} {% endfor %}
|
||||||
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
|
{% for volume in (systemd_defaults.volumes | default([])) + (item.volumes | default([])) %}--volume {{ volume }} {% endfor %}
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
{{ images[item.family] }} {{ systemd_defaults.command }}
|
||||||
env: "{{ item.env | default({}) }}"
|
delegate_to: localhost
|
||||||
state: started
|
|
||||||
restart_policy: unless-stopped
|
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
|
|
||||||
# DinD nodes
|
# DinD nodes
|
||||||
- name: Start DinD nodes (docker:27-dind)
|
- name: Start DinD nodes (docker:27-dind)
|
||||||
community.docker.docker_container:
|
command: >
|
||||||
name: "{{ item.name }}"
|
docker run -d --name {{ item.name }}
|
||||||
image: "docker:27-dind"
|
--network {{ docker_network }}
|
||||||
privileged: true
|
--privileged=true
|
||||||
environment: { DOCKER_TLS_CERTDIR: "" }
|
--env DOCKER_TLS_CERTDIR=""
|
||||||
networks: [ { name: "{{ docker_network }}" } ]
|
{% for port in item.publish | default([]) %}--publish {{ port }} {% endfor %}
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
--volume {{ item.name }}-docker:/var/lib/docker
|
||||||
volumes: [ "{{ item.name }}-docker:/var/lib/docker" ]
|
docker:27-dind
|
||||||
state: started
|
delegate_to: localhost
|
||||||
restart_policy: unless-stopped
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
|
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
|
|
||||||
# DOoD nodes (mount docker.sock)
|
# DOoD nodes (mount docker.sock)
|
||||||
- name: Start DOoD nodes (systemd + docker.sock mount)
|
- name: Start DOoD nodes (systemd + docker.sock mount)
|
||||||
community.docker.docker_container:
|
command: >
|
||||||
name: "{{ item.name }}"
|
docker run -d --name {{ item.name }}
|
||||||
image: "{{ images[item.family] }}"
|
--network {{ docker_network }}
|
||||||
networks: [ { name: "{{ docker_network }}" } ]
|
--privileged={{ systemd_defaults.privileged | lower }}
|
||||||
privileged: "{{ systemd_defaults.privileged }}"
|
--tmpfs {{ (systemd_defaults.tmpfs | default([])) | join(' --tmpfs ') }}
|
||||||
command: "{{ systemd_defaults.command }}"
|
--cap-add {{ (systemd_defaults.capabilities | default([])) | join(' --cap-add ') }}
|
||||||
volumes: "{{ (systemd_defaults.volumes | default([])) + ['/var/run/docker.sock:/var/run/docker.sock'] + (item.volumes | default([])) }}"
|
{% for port in item.publish | default([]) %}--publish {{ port }} {% endfor %}
|
||||||
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
|
{% for key, value in item.env | default({}) | dictsort %}--env {{ key }}={{ value }} {% endfor %}
|
||||||
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
|
{% for volume in (systemd_defaults.volumes | default([])) + ['/var/run/docker.sock:/var/run/docker.sock'] + (item.volumes | default([])) %}--volume {{ volume }} {% endfor %}
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
{{ images[item.family] }} {{ systemd_defaults.command }}
|
||||||
env: "{{ item.env | default({}) }}"
|
delegate_to: localhost
|
||||||
state: started
|
|
||||||
restart_policy: unless-stopped
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
---
|
---
|
||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars_files:
|
vars:
|
||||||
- ../presets/default.yml
|
# Получаем preset из переменной окружения или используем default
|
||||||
|
preset_name: "{{ lookup('env', 'MOLECULE_PRESET') | default('default') }}"
|
||||||
|
preset_file: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') | default('/tmp') }}/../presets/{{ preset_name }}.yml"
|
||||||
|
|
||||||
|
# Fallback значения если preset файл не найден
|
||||||
|
docker_network: labnet
|
||||||
|
hosts:
|
||||||
|
- name: u1
|
||||||
|
family: debian
|
||||||
|
groups: [test]
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: Load preset configuration
|
||||||
|
include_vars: "{{ preset_file }}"
|
||||||
|
when: preset_file is file
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Remove containers
|
- name: Remove containers
|
||||||
community.docker.docker_container:
|
community.docker.docker_container:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
driver:
|
driver:
|
||||||
name: docker
|
name: docker
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
# Платформы будут созданы динамически через preset файлы
|
||||||
|
- name: placeholder
|
||||||
|
image: ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy
|
||||||
|
pre_build_image: true
|
||||||
|
|
||||||
provisioner:
|
provisioner:
|
||||||
name: ansible
|
name: ansible
|
||||||
config_options:
|
config_options:
|
||||||
|
|||||||
@@ -25,5 +25,5 @@
|
|||||||
raw: ansible-galaxy collection install -r requirements.yml --force --no-deps --upgrade || true
|
raw: ansible-galaxy collection install -r requirements.yml --force --no-deps --upgrade || true
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- import_playbook: ../../roles/deploy.yml
|
- import_playbook: ../../deploy.yml
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
# Минимальный пресет для быстрого тестирования
|
# Стандартный пресет по умолчанию для тестирования
|
||||||
# Автор: Сергей Антропов
|
# Автор: Сергей Антропов
|
||||||
# Сайт: https://devops.org.ru
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
|||||||
# systemd-ready образы
|
# systemd-ready образы
|
||||||
images:
|
images:
|
||||||
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
||||||
|
rhel: "quay.io/centos/centos:stream9-systemd"
|
||||||
|
|
||||||
systemd_defaults:
|
systemd_defaults:
|
||||||
privileged: true
|
privileged: true
|
||||||
@@ -19,7 +20,10 @@ systemd_defaults:
|
|||||||
capabilities: ["SYS_ADMIN"]
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
|
||||||
hosts:
|
hosts:
|
||||||
# Минимальный набор - один хост
|
# Стандартный набор - 2 хоста для базового тестирования
|
||||||
- name: u1
|
- name: u1
|
||||||
family: debian
|
family: debian
|
||||||
groups: [test]
|
groups: [test, web]
|
||||||
|
- name: u2
|
||||||
|
family: rhel
|
||||||
|
groups: [test, web]
|
||||||
|
|||||||
38
molecule/presets/performance.yml
Normal file
38
molecule/presets/performance.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
# Пресет для тестирования производительности
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
docker_network: labnet
|
||||||
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
|
|
||||||
|
# systemd-ready образы
|
||||||
|
images:
|
||||||
|
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
||||||
|
rhel: "quay.io/centos/centos:stream9-systemd"
|
||||||
|
|
||||||
|
systemd_defaults:
|
||||||
|
privileged: true
|
||||||
|
command: "/sbin/init"
|
||||||
|
volumes:
|
||||||
|
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||||
|
tmpfs: ["/run", "/run/lock"]
|
||||||
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
|
||||||
|
hosts:
|
||||||
|
# Нагрузочное тестирование - 5 хостов
|
||||||
|
- name: perf1
|
||||||
|
family: debian
|
||||||
|
groups: [test, performance]
|
||||||
|
- name: perf2
|
||||||
|
family: debian
|
||||||
|
groups: [test, performance]
|
||||||
|
- name: perf3
|
||||||
|
family: rhel
|
||||||
|
groups: [test, performance]
|
||||||
|
- name: perf4
|
||||||
|
family: rhel
|
||||||
|
groups: [test, performance]
|
||||||
|
- name: perf5
|
||||||
|
family: debian
|
||||||
|
groups: [test, performance]
|
||||||
32
molecule/presets/security.yml
Normal file
32
molecule/presets/security.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
# Пресет для тестирования безопасности
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
docker_network: labnet
|
||||||
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
|
|
||||||
|
# systemd-ready образы
|
||||||
|
images:
|
||||||
|
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
||||||
|
rhel: "quay.io/centos/centos:stream9-systemd"
|
||||||
|
|
||||||
|
systemd_defaults:
|
||||||
|
privileged: true
|
||||||
|
command: "/sbin/init"
|
||||||
|
volumes:
|
||||||
|
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||||
|
tmpfs: ["/run", "/run/lock"]
|
||||||
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
|
||||||
|
hosts:
|
||||||
|
# Тестирование безопасности - 3 хоста с разными ОС
|
||||||
|
- name: sec1
|
||||||
|
family: debian
|
||||||
|
groups: [test, security, web]
|
||||||
|
- name: sec2
|
||||||
|
family: rhel
|
||||||
|
groups: [test, security, db]
|
||||||
|
- name: sec3
|
||||||
|
family: debian
|
||||||
|
groups: [test, security, api]
|
||||||
25
molecule/presets/test.yml
Normal file
25
molecule/presets/test.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
# Минимальный пресет для быстрого тестирования
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
docker_network: labnet
|
||||||
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
|
|
||||||
|
# systemd-ready образы
|
||||||
|
images:
|
||||||
|
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
|
||||||
|
|
||||||
|
systemd_defaults:
|
||||||
|
privileged: true
|
||||||
|
command: "/sbin/init"
|
||||||
|
volumes:
|
||||||
|
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||||
|
tmpfs: ["/run", "/run/lock"]
|
||||||
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
|
||||||
|
hosts:
|
||||||
|
# Минимальный набор - один хост
|
||||||
|
- name: u1
|
||||||
|
family: debian
|
||||||
|
groups: [test]
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
# Плейбук для развертывания ролей
|
|
||||||
# Автор: Сергей Антропов
|
|
||||||
# Сайт: https://devops.org.ru
|
|
||||||
|
|
||||||
- name: Test nginx role
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
roles:
|
|
||||||
- nginx
|
|
||||||
tags:
|
|
||||||
- nginx
|
|
||||||
- test
|
|
||||||
52
scripts/test-playbook.yml
Normal file
52
scripts/test-playbook.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
# Простой тестовый playbook для проверки 3 контейнеров
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
- name: Test containers connectivity
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- name: Check container u1 (Debian)
|
||||||
|
command: docker exec u1 echo "Hello from u1"
|
||||||
|
register: u1_result
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check container u2 (RHEL)
|
||||||
|
command: docker exec u2 echo "Hello from u2"
|
||||||
|
register: u2_result
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check container u3 (Debian)
|
||||||
|
command: docker exec u3 echo "Hello from u3"
|
||||||
|
register: u3_result
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display results
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "u1 (Debian): {{ u1_result.stdout }}"
|
||||||
|
- "u2 (RHEL): {{ u2_result.stdout }}"
|
||||||
|
- "u3 (Debian): {{ u3_result.stdout }}"
|
||||||
|
|
||||||
|
- name: Install nginx on u1
|
||||||
|
command: docker exec u1 bash -c "apt-get update && apt-get install -y nginx"
|
||||||
|
register: nginx_u1
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Install nginx on u2
|
||||||
|
command: docker exec u2 bash -c "yum install -y nginx"
|
||||||
|
register: nginx_u2
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Install nginx on u3
|
||||||
|
command: docker exec u3 bash -c "apt-get update && apt-get install -y nginx"
|
||||||
|
register: nginx_u3
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display nginx installation results
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "Nginx installation on u1: {{ 'SUCCESS' if nginx_u1.rc == 0 else 'FAILED' }}"
|
||||||
|
- "Nginx installation on u2: {{ 'SUCCESS' if nginx_u2.rc == 0 else 'FAILED' }}"
|
||||||
|
- "Nginx installation on u3: {{ 'SUCCESS' if nginx_u3.rc == 0 else 'FAILED' }}"
|
||||||
83
scripts/test-standart.sh
Executable file
83
scripts/test-standart.sh
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Скрипт для тестирования с preset standart
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Запуск тестирования с preset standart..."
|
||||||
|
|
||||||
|
# Очищаем старые контейнеры
|
||||||
|
echo "🧹 Очистка старых контейнеров..."
|
||||||
|
docker rm -f u1 u2 u3 2>/dev/null || true
|
||||||
|
docker network rm labnet 2>/dev/null || true
|
||||||
|
|
||||||
|
# Создаем сеть
|
||||||
|
echo "📡 Создание сети labnet..."
|
||||||
|
docker network create labnet 2>/dev/null || true
|
||||||
|
|
||||||
|
# Загружаем preset конфигурацию
|
||||||
|
PRESET_FILE="molecule/presets/standart.yml"
|
||||||
|
if [ ! -f "$PRESET_FILE" ]; then
|
||||||
|
echo "❌ Ошибка: Пресет файл $PRESET_FILE не найден!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Извлекаем конфигурацию из preset файла
|
||||||
|
echo "📋 Загрузка конфигурации из $PRESET_FILE..."
|
||||||
|
|
||||||
|
# Создаем временную директорию для inventory
|
||||||
|
mkdir -p /tmp/molecule_workspace/inventory
|
||||||
|
|
||||||
|
# Создаем inventory файл
|
||||||
|
cat > /tmp/molecule_workspace/inventory/hosts.ini << EOF
|
||||||
|
[all]
|
||||||
|
localhost ansible_connection=local
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "📄 Создан inventory файл:"
|
||||||
|
cat /tmp/molecule_workspace/inventory/hosts.ini
|
||||||
|
|
||||||
|
# Запускаем контейнеры
|
||||||
|
echo "🐳 Создание контейнеров..."
|
||||||
|
|
||||||
|
# u1 - Debian
|
||||||
|
echo "Создание u1 (Debian)..."
|
||||||
|
docker run -d --name u1 \
|
||||||
|
--network labnet \
|
||||||
|
-p 2201:22 \
|
||||||
|
ubuntu:20.04 \
|
||||||
|
bash -c "apt-get update && apt-get install -y openssh-server && service ssh start && sleep infinity"
|
||||||
|
|
||||||
|
# u2 - Debian (временно используем Ubuntu вместо CentOS)
|
||||||
|
echo "Создание u2 (Debian)..."
|
||||||
|
docker run -d --name u2 \
|
||||||
|
--network labnet \
|
||||||
|
-p 2202:22 \
|
||||||
|
ubuntu:20.04 \
|
||||||
|
bash -c "apt-get update && apt-get install -y openssh-server && service ssh start && sleep infinity"
|
||||||
|
|
||||||
|
# u3 - Debian
|
||||||
|
echo "Создание u3 (Debian)..."
|
||||||
|
docker run -d --name u3 \
|
||||||
|
--network labnet \
|
||||||
|
-p 2203:22 \
|
||||||
|
ubuntu:20.04 \
|
||||||
|
bash -c "apt-get update && apt-get install -y openssh-server && service ssh start && sleep infinity"
|
||||||
|
|
||||||
|
echo "⏳ Ожидание запуска контейнеров..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Проверяем статус контейнеров
|
||||||
|
echo "📊 Статус контейнеров:"
|
||||||
|
docker ps --filter "name=u[123]" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
|
||||||
|
|
||||||
|
# Запускаем тесты
|
||||||
|
echo "🧪 Запуск тестов..."
|
||||||
|
ansible-playbook -i /tmp/molecule_workspace/inventory/hosts.ini scripts/test-playbook.yml
|
||||||
|
|
||||||
|
echo "🧹 Очистка контейнеров..."
|
||||||
|
docker rm -f u1 u2 u3 2>/dev/null || true
|
||||||
|
docker network rm labnet 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "✅ Тестирование завершено!"
|
||||||
Reference in New Issue
Block a user