From e6573a7d69cf4357030902f96644d09e5f4141d4 Mon Sep 17 00:00:00 2001 From: dorley174 Date: Thu, 12 Mar 2026 13:32:53 +0300 Subject: [PATCH 1/5] added unnecessary vargart files --- .../default/virtualbox/action_set_name | 1 + .../machines/default/virtualbox/creator_uid | 1 + .vagrant/machines/default/virtualbox/id | 1 + .../machines/default/virtualbox/index_uuid | 1 + .vagrant/rgloader/loader.rb | 12 +++ Vagrantfile | 21 +++++ ansible/README.md | 2 +- ansible/docs/LAB05.md | 86 ++++++++++--------- 8 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 .vagrant/machines/default/virtualbox/action_set_name create mode 100644 .vagrant/machines/default/virtualbox/creator_uid create mode 100644 .vagrant/machines/default/virtualbox/id create mode 100644 .vagrant/machines/default/virtualbox/index_uuid create mode 100644 .vagrant/rgloader/loader.rb create mode 100644 Vagrantfile diff --git a/.vagrant/machines/default/virtualbox/action_set_name b/.vagrant/machines/default/virtualbox/action_set_name new file mode 100644 index 0000000000..75d209792b --- /dev/null +++ b/.vagrant/machines/default/virtualbox/action_set_name @@ -0,0 +1 @@ +1772053285 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/creator_uid b/.vagrant/machines/default/virtualbox/creator_uid new file mode 100644 index 0000000000..c227083464 --- /dev/null +++ b/.vagrant/machines/default/virtualbox/creator_uid @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/id b/.vagrant/machines/default/virtualbox/id new file mode 100644 index 0000000000..d03e7221f3 --- /dev/null +++ b/.vagrant/machines/default/virtualbox/id @@ -0,0 +1 @@ +cc19f777-65a9-4b56-a797-c78cb0a2b7b6 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/index_uuid b/.vagrant/machines/default/virtualbox/index_uuid new file mode 100644 index 0000000000..fcd59f28ab --- /dev/null +++ b/.vagrant/machines/default/virtualbox/index_uuid @@ -0,0 +1 @@ +0f4952a08a2e4c1f99ba386907a66913 \ No newline at end of file diff --git a/.vagrant/rgloader/loader.rb b/.vagrant/rgloader/loader.rb new file mode 100644 index 0000000000..b6c81bf31b --- /dev/null +++ b/.vagrant/rgloader/loader.rb @@ -0,0 +1,12 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +# This file loads the proper rgloader/loader.rb file that comes packaged +# with Vagrant so that encoded files can properly run with Vagrant. + +if ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] + require File.expand_path( + "rgloader/loader", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) +else + raise "Encoded files can't be read outside of the Vagrant installer." +end diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000000..7bf76d526b --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,21 @@ +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/jammy64" + config.vm.hostname = "lab05" + + # ВАЖНО: отключаем шаринг папки проекта в VM + # (часто ломается из-за кириллицы/пробелов в пути + нам не нужен репозиторий в VM) + config.vm.synced_folder ".", "/vagrant", disabled: true + + # Пробрасываем порты на Windows-хост + # host_ip "0.0.0.0" нужно, чтобы WSL мог подключиться к проброшенному порту через IP Windows-хоста. + config.vm.network "forwarded_port", guest: 22, host: 2222, host_ip: "0.0.0.0", id: "ssh", auto_correct: true + config.vm.network "forwarded_port", guest: 5000, host: 5000, host_ip: "0.0.0.0", id: "app", auto_correct: true + + config.ssh.insert_key = true + + config.vm.provider "virtualbox" do |vb| + vb.name = "lab05-ansible" + vb.memory = 2048 + vb.cpus = 2 + end +end diff --git a/ansible/README.md b/ansible/README.md index 1a1d813614..be50333ede 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -1,6 +1,6 @@ # Lab05 — Ansible -См. `labs/lab05.md` (задание) и `ansible/docs/LAB05.md` (отчёт). +Check `labs/lab05.md` (task) ans `ansible/docs/LAB05.md` (report). Быстрые команды: diff --git a/ansible/docs/LAB05.md b/ansible/docs/LAB05.md index e922b27c9d..328063a55f 100644 --- a/ansible/docs/LAB05.md +++ b/ansible/docs/LAB05.md @@ -1,43 +1,51 @@ -# LAB05 — Ansible Fundamentals (отчёт) +# LAB05 — Ansible Fundamentals (report) -> Этот файл — готовый шаблон. Выполните команды из инструкции и вставьте выводы в отмеченные места. +> LAB05 - DevOps ## 1. Architecture Overview - **Ansible version**: - - Команда: `ansible --version` + - Command: `ansible --version` - Вывод: ```text -PASTE_HERE +ansible [core 2.20.3] + config file = None + configured module search path = ['/home/dorley/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] + ansible python module location = /home/dorley/.local/share/pipx/venvs/ansible-core/lib/python3.12/site-packages/ansible + ansible collection location = /home/dorley/.ansible/collections:/usr/share/ansible/collections + executable location = /home/dorley/.local/bin/ansible + python version = 3.12.3 (main, Jan 22 2026, 20:57:42) [GCC 13.3.0] (/home/dorley/.local/share/pipx/venvs/ansible-core/bin/python) + jinja version = 3.1.6 + pyyaml version = 6.0.3 (with libyaml v0.2.5) ``` - **Target VM**: - - OS (команда `lsb_release -a`): + - OS (command `lsb_release -a`): ```text PASTE_HERE ``` -- **Роли (roles) vs монолитный playbook** - - Роли дают переиспользуемость, чистую структуру, читаемость и возможность тестировать/переносить куски автоматизации между проектами. +- **Roles vs monolyte playbook** + - Roles give reusing, clean arch,, readability and ability to test or move automatization parts between projects ## 2. Roles Documentation ### role: common -- **Purpose:** базовая подготовка ОС (обновление apt cache, утилиты, часовой пояс). +- **Purpose:** base ОС setup (update apt cache, utilits). - **Variables (defaults):** `common_packages`, `common_timezone`. -- **Handlers:** нет. -- **Dependencies:** нет (timezone настраивается через `timedatectl` и включается переменной `common_set_timezone`). +- **Handlers:** - no. +- **Dependencies:** -no (timezone setups using `timedatectl` and starts with `common_set_timezone`). ### role: docker -- **Purpose:** установка Docker Engine из официального репозитория + настройка сервиса. +- **Purpose:** setup Docker Engine from repo + service setup. - **Variables (defaults):** `docker_user`, `docker_packages`, `docker_gpg_key_url`, `docker_keyring_path`. - **Handlers:** `restart docker`. -- **Dependencies:** нет (используются builtin модули). +- **Dependencies:** no. ### role: app_deploy -- **Purpose:** логин в реестр, pull образа, запуск контейнера с приложением, health-check. +- **Purpose:** login in reestr, pull of image, container start, health-check. - **Variables (defaults):** `app_name`, `app_port`, `container_port`, `app_restart_policy`, `app_env`, `docker_registry`. - **Vault variables:** `dockerhub_username`, `dockerhub_password`, `docker_image`, `docker_image_tag`. - **Handlers:** `restart app container`. @@ -45,89 +53,89 @@ PASTE_HERE ## 3. Idempotency Demonstration -### 3.1 Первый запуск provision.yml -Команда: +### 3.1 First start of provision.yml +Command: ```bash ansible-playbook playbooks/provision.yml ``` -Вывод: +Output: ```text PASTE_PROVISION_RUN_1 ``` -### 3.2 Второй запуск provision.yml -Команда: +### 3.2 Secondary start of provision.yml +Command: ```bash ansible-playbook playbooks/provision.yml ``` -Вывод: +Output: ```text PASTE_PROVISION_RUN_2 ``` -### 3.3 Анализ -- На первом запуске задачи меняли систему (установка пакетов, добавление репозиториев, запуск сервисов) → `changed`. -- На втором запуске желаемое состояние уже достигнуто → почти всё `ok`, без лишних изменений. +### 3.3 Analize +- While pulling at first time, i changed the system (packet setup, repo adding) → `changed`. +- WWhile pulling at 2nd time, wishing state has been reached -> almost all `ok` without any chhanges ## 4. Ansible Vault Usage -### 4.1 Как хранятся секреты -- Секреты (Docker Hub username + access token) хранятся в `ansible/group_vars/all.yml`, зашифрованном Ansible Vault. +### 4.1 How to store secrets +- Secrets (Docker Hub username + access token) stores at `ansible/group_vars/all.yml`, encrypted by Ansible Vault. -### 4.2 Доказательство шифрования -Покажите первые строки файла (команда `head -n 5 ansible/group_vars/all.yml`): +### 4.2 Proof of encryption +just use `head -n 5 ansible/group_vars/all.yml`: ```text PASTE_VAULT_HEADER ``` -### 4.3 Стратегия хранения пароля Vault -- Вариант A: вводить пароль через `--ask-vault-pass`. -- Вариант B: `.vault_pass` (права 600) + добавление в `.gitignore`. +### 4.3 Password Storage Strategy Vault +- Option A: enter the password via `--ask-vault-pass`. +- Option B: `.vault_pass` (rights 600) + add to `.gitignore`. ## 5. Deployment Verification -### 5.1 Запуск deploy.yml -Команда: +### 5.1 Start deploy.yml +Command: ```bash ansible-playbook playbooks/deploy.yml --ask-vault-pass ``` -Вывод: +Output: ```text PASTE_DEPLOY_OUTPUT ``` -### 5.2 Проверка контейнера -Команда: +### 5.2 Container check +Command: ```bash ansible webservers -a "docker ps" ``` -Вывод: +Output: ```text PASTE_DOCKER_PS ``` -### 5.3 Проверка health endpoint -Команды: +### 5.3 Check health endpoint +Command: ```bash curl http://:5000/health curl http://:5000/ ``` -Вывод: +Output: ```text PASTE_CURL_OUTPUT @@ -151,4 +159,4 @@ PASTE_CURL_OUTPUT - Чтобы секреты не хранились в открытом виде в репозитории, но могли использоваться в автоматизации. ## 7. Challenges (optional) -- PASTE_YOUR_NOTES +- probably have not. the hardest challenge is to understand lab4 and lab5 work principle: why we need a lot of utilities, vm's, clouds an so on. it is harder than just setup ansible folder using stack.overflow guides from advices of some guys who recommended some features. =\ From 656893c921ef1b365a437a24a65445f1542cfd0a Mon Sep 17 00:00:00 2001 From: dorley174 Date: Thu, 12 Mar 2026 19:12:12 +0300 Subject: [PATCH 2/5] added lab06 solution --- .github/workflows/ansible-deploy.yml | 97 +++ .gitignore | 1 + .../default/virtualbox/action_set_name | 2 +- .vagrant/machines/default/virtualbox/id | 2 +- .../machines/default/virtualbox/index_uuid | 2 +- Vagrantfile | 11 +- ansible/.ansible-lint | 3 + ansible/README.md | 37 +- ansible/docs/LAB06.md | 120 ++++ ansible/docs/kab07_overview.md | 605 ++++++++++++++++++ ansible/group_vars/all.yml.example | 14 +- ansible/group_vars/webservers.yml | 12 +- ansible/inventory/group_vars/webservers.yml | 15 + ansible/inventory/hosts.ini | 16 +- ansible/playbooks/deploy.yml | 8 +- ansible/playbooks/provision.yml | 9 +- ansible/playbooks/site.yml | 11 +- ansible/requirements.yml | 1 + ansible/roles/app_deploy/defaults/main.yml | 20 - ansible/roles/app_deploy/handlers/main.yml | 6 - ansible/roles/app_deploy/tasks/main.yml | 77 --- ansible/roles/common/defaults/main.yml | 19 +- ansible/roles/common/tasks/main.yml | 114 +++- ansible/roles/docker/handlers/main.yml | 2 +- ansible/roles/docker/tasks/main.yml | 230 ++++--- ansible/roles/web_app/defaults/main.yml | 45 ++ ansible/roles/web_app/meta/main.yml | 7 + ansible/roles/web_app/tasks/main.yml | 89 +++ ansible/roles/web_app/tasks/wipe.yml | 55 ++ .../web_app/templates/docker-compose.yml.j2 | 27 + 30 files changed, 1417 insertions(+), 240 deletions(-) create mode 100644 .github/workflows/ansible-deploy.yml create mode 100644 ansible/.ansible-lint create mode 100644 ansible/docs/LAB06.md create mode 100644 ansible/docs/kab07_overview.md create mode 100644 ansible/inventory/group_vars/webservers.yml delete mode 100644 ansible/roles/app_deploy/defaults/main.yml delete mode 100644 ansible/roles/app_deploy/handlers/main.yml delete mode 100644 ansible/roles/app_deploy/tasks/main.yml create mode 100644 ansible/roles/web_app/defaults/main.yml create mode 100644 ansible/roles/web_app/meta/main.yml create mode 100644 ansible/roles/web_app/tasks/main.yml create mode 100644 ansible/roles/web_app/tasks/wipe.yml create mode 100644 ansible/roles/web_app/templates/docker-compose.yml.j2 diff --git a/.github/workflows/ansible-deploy.yml b/.github/workflows/ansible-deploy.yml new file mode 100644 index 0000000000..b2713dc4c9 --- /dev/null +++ b/.github/workflows/ansible-deploy.yml @@ -0,0 +1,97 @@ +name: Ansible Deployment + +on: + push: + branches: [ lab06, master ] + paths: + - 'ansible/**' + - '.github/workflows/ansible-deploy.yml' + pull_request: + branches: [ lab06, master ] + paths: + - 'ansible/**' + - '.github/workflows/ansible-deploy.yml' + +jobs: + lint: + name: Ansible Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Ansible + ansible-lint + run: | + python -m pip install --upgrade pip + pip install ansible ansible-lint + + - name: Install Ansible collections + run: | + ansible-galaxy collection install -r ansible/requirements.yml + + - name: Run ansible-lint + run: | + cd ansible + ansible-lint -p playbooks/*.yml roles + + deploy: + name: Deploy Application + needs: lint + + # For a local Vagrant/VirtualBox setup behind NAT, the simplest free option + # is a self-hosted Linux runner on the same machine (for example, inside WSL2). + runs-on: [self-hosted, linux] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Ansible (self-hosted) + shell: bash + run: | + set -euo pipefail + python3 -m pip install --user --upgrade pip + python3 -m pip install --user ansible + + - name: Add user-local bin to PATH + shell: bash + run: | + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install Ansible collections + shell: bash + run: | + set -euo pipefail + ansible-galaxy collection install -r ansible/requirements.yml + + - name: Deploy with Ansible + shell: bash + env: + ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }} + run: | + set -euo pipefail + cd ansible + + if [[ -n "${ANSIBLE_VAULT_PASSWORD:-}" ]]; then + trap 'rm -f /tmp/vault_pass' EXIT + printf '%s' "${ANSIBLE_VAULT_PASSWORD}" > /tmp/vault_pass + ansible-playbook playbooks/deploy.yml --vault-password-file /tmp/vault_pass + else + ansible-playbook playbooks/deploy.yml + fi + + - name: Verify deployment + shell: bash + env: + VM_HOST: ${{ secrets.VM_HOST }} + run: | + set -euo pipefail + HOST="${VM_HOST:-127.0.0.1}" + curl --retry 10 --retry-delay 3 --retry-connrefused -fsS "http://${HOST}:8000/health" + curl --retry 10 --retry-delay 3 --retry-connrefused -fsS "http://${HOST}:8000/" diff --git a/.gitignore b/.gitignore index e050328db5..3ae73a858e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ test .env +.venv key.json # --- Ansible --- *.retry diff --git a/.vagrant/machines/default/virtualbox/action_set_name b/.vagrant/machines/default/virtualbox/action_set_name index 75d209792b..77f6fb8046 100644 --- a/.vagrant/machines/default/virtualbox/action_set_name +++ b/.vagrant/machines/default/virtualbox/action_set_name @@ -1 +1 @@ -1772053285 \ No newline at end of file +1773325600 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/id b/.vagrant/machines/default/virtualbox/id index d03e7221f3..9bd219e5e6 100644 --- a/.vagrant/machines/default/virtualbox/id +++ b/.vagrant/machines/default/virtualbox/id @@ -1 +1 @@ -cc19f777-65a9-4b56-a797-c78cb0a2b7b6 \ No newline at end of file +5f4a1e02-46c6-4dcd-817a-01783b29dfd2 \ No newline at end of file diff --git a/.vagrant/machines/default/virtualbox/index_uuid b/.vagrant/machines/default/virtualbox/index_uuid index fcd59f28ab..90907a7cd0 100644 --- a/.vagrant/machines/default/virtualbox/index_uuid +++ b/.vagrant/machines/default/virtualbox/index_uuid @@ -1 +1 @@ -0f4952a08a2e4c1f99ba386907a66913 \ No newline at end of file +41695340a0c0459bb1ec1e9674b9fcae \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile index 7bf76d526b..a204b68518 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -2,14 +2,15 @@ Vagrant.configure("2") do |config| config.vm.box = "ubuntu/jammy64" config.vm.hostname = "lab05" - # ВАЖНО: отключаем шаринг папки проекта в VM - # (часто ломается из-за кириллицы/пробелов в пути + нам не нужен репозиторий в VM) + # Disable project folder sharing inside the VM. + # This avoids common Windows path issues (spaces, Cyrillic characters) + # and is not needed for this lab workflow. config.vm.synced_folder ".", "/vagrant", disabled: true - # Пробрасываем порты на Windows-хост - # host_ip "0.0.0.0" нужно, чтобы WSL мог подключиться к проброшенному порту через IP Windows-хоста. + # Forward ports to the Windows host. + # host_ip "0.0.0.0" lets WSL2 reach the forwarded ports through the host. config.vm.network "forwarded_port", guest: 22, host: 2222, host_ip: "0.0.0.0", id: "ssh", auto_correct: true - config.vm.network "forwarded_port", guest: 5000, host: 5000, host_ip: "0.0.0.0", id: "app", auto_correct: true + config.vm.network "forwarded_port", guest: 8000, host: 8000, host_ip: "0.0.0.0", id: "app", auto_correct: true config.ssh.insert_key = true diff --git a/ansible/.ansible-lint b/ansible/.ansible-lint new file mode 100644 index 0000000000..3ab93c253f --- /dev/null +++ b/ansible/.ansible-lint @@ -0,0 +1,3 @@ +skip_list: + - key-order + - var-naming[no-role-prefix] diff --git a/ansible/README.md b/ansible/README.md index 29ea9a8d67..60c1ec3a54 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -1,6 +1,19 @@ -# Lab05 — Ansible +# Lab05/Lab06 — Ansible -Check `labs/lab05.md` (task) ans `ansible/docs/LAB05.md` (report). +See: +- `labs/lab05.md` — assignment +- `ansible/docs/LAB05.md` — report template +- `labs/lab06.md` — assignment +- `ansible/docs/LAB06.md` — report +- `ansible/docs/LOCAL_VALIDATION_WINDOWS.md` — Windows + WSL2 local validation guide + +## Workflow badge + +After creating your GitHub repository, replace `OWNER/REPO` in the snippet below: + +```md +[![Ansible Deployment](https://github.com/OWNER/REPO/actions/workflows/ansible-deploy.yml/badge.svg)](https://github.com/OWNER/REPO/actions/workflows/ansible-deploy.yml) +``` ## Quick start @@ -17,6 +30,22 @@ ansible all -m ping ansible-playbook playbooks/provision.yml ansible-playbook playbooks/provision.yml -# Deploy the application (uses Ansible Vault) -ansible-playbook playbooks/deploy.yml --ask-vault-pass +# Deploy the application with Docker Compose +ansible-playbook playbooks/deploy.yml + +# Useful tag examples (Lab06) +ansible-playbook playbooks/provision.yml --list-tags +ansible-playbook playbooks/provision.yml --tags "docker_install" +ansible-playbook playbooks/provision.yml --skip-tags "common" +ansible-playbook playbooks/deploy.yml --tags "web_app" + +# Wipe only (double-gated: variable + tag) +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe ``` + +## CI/CD (GitHub Actions) + +Workflow file: `.github/workflows/ansible-deploy.yml`. + +For Vagrant/VirtualBox setups behind NAT, prefer a **self-hosted Linux runner** on the same machine +where you run Ansible (for example, WSL2 Ubuntu). This avoids inbound SSH exposure and stays free. diff --git a/ansible/docs/LAB06.md b/ansible/docs/LAB06.md new file mode 100644 index 0000000000..5374f4a080 --- /dev/null +++ b/ansible/docs/LAB06.md @@ -0,0 +1,120 @@ +# Lab 06 — Ansible Roles Refactoring and Local Deployment Validation + +## Summary + +This lab was implemented and validated in a fully local and free environment on Windows 11 using VirtualBox, Vagrant, WSL2 Ubuntu, Ansible, and Docker. + +The infrastructure provisioning flow works correctly, static validation passes, and the deployment playbook reaches the final Docker Compose stage. The remaining issue is a Docker Compose YAML rendering error in the generated `docker-compose.yml`, which prevents the application from being started successfully. + +## Environment + +- Host OS: Windows 11 +- Virtualization: VirtualBox +- VM orchestration: Vagrant +- Automation runtime: WSL2 Ubuntu +- Configuration management: Ansible +- Container runtime: Docker Engine + Docker Compose plugin +- CI approach: prepared for self-hosted Linux runner +- Validation model: fully local, no paid cloud resources used + +## Implemented Changes + +### Task 1 — Refactor existing roles with `block`, `rescue`, and `always` +- Refactored `common` role to use structured error handling blocks. +- Refactored `docker` role to use structured error handling blocks. +- Added meaningful tags to role tasks. + +### Task 2 — Create a deploy role based on Docker Compose +- Replaced the old deployment logic with a `web_app` role. +- Added Docker dependency through role metadata. +- Implemented application deployment using Docker Compose v2. +- Added optional wipe logic with dedicated tags and safety checks. + +### Task 3 — Add selective wipe support +- Implemented wipe-only workflow using `web_app_wipe` tags. +- Added safety condition to prevent accidental destructive cleanup. + +### Task 4 — Prepare local free execution model +- Configured the project to work against a Vagrant VM. +- Validated SSH access from WSL2 to the VM through forwarded port `2222`. +- Used Windows host IP from WSL2 (`172.19.144.1`) for Ansible connectivity. + +### Task 5 — Documentation +- Added local validation instructions. +- Prepared reproducible local execution steps for Windows + WSL2. + +## Validation Results + +### Static Validation +The following checks completed successfully: + +- `ansible all -i inventory/hosts.ini -m ping` +- `ansible-playbook -i inventory/hosts.ini playbooks/provision.yml --syntax-check` +- `ansible-playbook -i inventory/hosts.ini playbooks/deploy.yml --syntax-check` +- `ansible-lint --format=pep8 playbooks/*.yml roles` + +### Provisioning Validation +`playbooks/provision.yml` completed successfully. + +Validated outcomes: +- base packages installed +- Docker repository configured +- Docker Engine installed +- Docker service enabled and running +- target host reachable and manageable by Ansible + +### Deployment Validation +`playbooks/deploy.yml` was executed successfully up to the application deployment stage. + +Validated outcomes: +- Docker role dependency executed correctly +- application directory was created +- `docker-compose.yml` was rendered and copied to target host +- Docker image reference was resolved correctly +- image pull from Docker Hub succeeded after transient network timeout recovery + +### Final Blocking Issue +The deployment currently stops at the Docker Compose execution stage with the following error: + +`yaml: line 12, column 30: mapping values are not allowed in this context` + +This indicates that the generated `docker-compose.yml` still contains a YAML formatting issue and requires one final template fix before the application can be started successfully. + +## Issues Encountered During Validation + +### 1. WSL2 to localhost forwarding +WSL2 could not reach the forwarded SSH port on `127.0.0.1:2222`. +This was resolved by using the Windows host IP visible from WSL2: + +- `172.19.144.1:2222` + +### 2. Ansible configuration ignored in writable directory +When the project was executed from a Windows-mounted path, Ansible ignored the local `ansible.cfg`. +This was resolved by copying the project to the native WSL filesystem and explicitly exporting: + +`ANSIBLE_CONFIG=/home/dorley/work/ansible/ansible.cfg` + +### 3. Missing deployment variable +Initial deployment failed because `dockerhub_username` was undefined. +This was resolved by defining the variable in inventory group variables. + +### 4. Transient Docker Hub network timeout +One deployment attempt failed with a TLS handshake timeout when pulling the image from Docker Hub. +Manual pull confirmed that the image reference was correct and the issue was transient. + +### 5. Final Docker Compose YAML error +The remaining unresolved issue is a YAML syntax problem in the generated Compose file. +This is the last known blocker preventing the application from starting. + +## Conclusion + +The lab is largely completed and validated: + +- provisioning works +- role refactoring is implemented +- static checks pass +- deployment logic is mostly correct +- the local free execution model on Windows + WSL2 + Vagrant is valid + +The only remaining issue is the final Docker Compose YAML rendering error in the generated application manifest. Once the template is corrected, the deployment should complete successfully. + diff --git a/ansible/docs/kab07_overview.md b/ansible/docs/kab07_overview.md new file mode 100644 index 0000000000..ef0b078c1d --- /dev/null +++ b/ansible/docs/kab07_overview.md @@ -0,0 +1,605 @@ +# Local Validation Guide (Windows + WSL2 + Vagrant + VirtualBox) + +This guide is designed for a **fully local and free** Lab06 workflow on **Windows**. +It assumes: + +- Windows 10/11 +- VirtualBox +- Vagrant +- WSL2 with Ubuntu +- GitHub repository (optional, only for CI/CD) + +--- + +## 1. Recommended Setup + +### Windows side + +Install and verify: + +```powershell +vagrant --version +VBoxManage --version +git --version +``` + +Recommended Vagrant plugins check: + +```powershell +vagrant plugin list +``` + +### WSL2 Ubuntu side + +Install and verify: + +```bash +wsl --status +python3 --version +pip3 --version +ssh -V +git --version +``` + +Install Ansible in WSL2 if missing: + +```bash +python3 -m pip install --user --upgrade pip +python3 -m pip install --user ansible ansible-lint +printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> ~/.bashrc +source ~/.bashrc +ansible --version +ansible-lint --version +``` + +--- + +## 2. Start the VM + +Run in **PowerShell** from the repository root: + +```powershell +vagrant up +vagrant status +vagrant port +``` + +Expected important forwarded ports: + +- guest SSH `22` -> host `2222` +- guest app `8000` -> host `8000` + +If ports changed because of `auto_correct`, update the inventory accordingly. + +--- + +## 3. Prepare the SSH key for Ansible + +The Vagrant private key is usually stored under the project directory on Windows. +Copy it into WSL and fix permissions. + +Example from WSL: + +```bash +mkdir -p ~/.ssh +cp /mnt/c/path/to/your/repo/.vagrant/machines/default/virtualbox/private_key ~/.ssh/lab05_vagrant_key +chmod 600 ~/.ssh/lab05_vagrant_key +``` + +Check SSH connectivity: + +```bash +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@127.0.0.1 +``` + +If `127.0.0.1` does not work from WSL2, detect the Windows host IP and try again: + +```bash +WIN_HOST_IP=$(grep -m1 nameserver /etc/resolv.conf | awk '{print $2}') +echo "$WIN_HOST_IP" +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@"$WIN_HOST_IP" +``` + +--- + +## 4. Inventory Settings + +Default local-friendly inventory example: + +```ini +[webservers] +vagrant1 ansible_host=127.0.0.1 ansible_port=2222 ansible_user=vagrant ansible_ssh_private_key_file=~/.ssh/lab05_vagrant_key + +[webservers:vars] +ansible_python_interpreter=/usr/bin/python3 +``` + +If WSL2 cannot reach `127.0.0.1:2222`, replace `ansible_host` with the detected Windows host IP. + +--- + +## 5. Install Ansible collections + +Run in WSL2 from the repository root: + +```bash +cd ansible +ansible-galaxy collection install -r requirements.yml +``` + +Verify installed collections: + +```bash +ansible-galaxy collection list | grep -E 'community.docker|community.general|ansible.posix' +``` + +--- + +## 6. Basic validation commands + +### Connectivity + +```bash +cd ansible +ansible all -m ping +``` + +### Syntax and task listing + +```bash +ansible-playbook playbooks/provision.yml --syntax-check +ansible-playbook playbooks/deploy.yml --syntax-check +ansible-playbook playbooks/provision.yml --list-tags +ansible-playbook playbooks/deploy.yml --list-tasks +ansible-playbook playbooks/deploy.yml --list-tasks --tags web_app +``` + +### Lint + +```bash +ansible-lint -p . +``` + +--- + +## 7. Provisioning validation + +```bash +cd ansible +ansible-playbook playbooks/provision.yml +ansible-playbook playbooks/provision.yml +``` + +The second run should be mostly idempotent. + +Selective tag checks: + +```bash +ansible-playbook playbooks/provision.yml --tags docker +ansible-playbook playbooks/provision.yml --tags docker_install +ansible-playbook playbooks/provision.yml --tags packages +ansible-playbook playbooks/provision.yml --skip-tags common +``` + +--- + +## 8. Deployment validation + +```bash +cd ansible +ansible-playbook playbooks/deploy.yml +ansible-playbook playbooks/deploy.yml +``` + +Verify on the target VM: + +```bash +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@127.0.0.1 +sudo docker ps +sudo docker compose -f /opt/devops-info-service/docker-compose.yml ps +curl http://127.0.0.1:8000/health +curl http://127.0.0.1:8000/ +``` + +From WSL2 / host side: + +```bash +curl http://127.0.0.1:8000/health +curl http://127.0.0.1:8000/ +``` + +If WSL2 cannot reach Windows localhost, replace `127.0.0.1` with the Windows host IP. + +--- + +## 9. Wipe logic validation + +### Wipe only + +```bash +cd ansible +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe +``` + +### Clean reinstall + +```bash +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" +``` + +### Safety check + +```bash +ansible-playbook playbooks/deploy.yml --tags web_app_wipe +``` + +Expected result: wipe tasks are selected by tag but skipped by the boolean guard. + +--- + +## 10. GitHub Actions self-hosted runner (free local option) + +Recommended approach: + +- Create the self-hosted runner in **WSL2 Ubuntu**, not in native Windows. +- Use labels that include `self-hosted` and `linux`. +- Run the runner only when you want to test CI locally. + +Why this is preferred: + +- the workflow uses Bash and Linux paths +- Ansible support is simpler on Linux +- no cloud VM is required +- no paid service is required + +Useful checks on the runner machine: + +```bash +python3 --version +ansible --version +ansible-galaxy collection list | grep -E 'community.docker|community.general|ansible.posix' +curl --version +``` + +Suggested repository secrets: + +- `ANSIBLE_VAULT_PASSWORD` (only if you really use Vault) +- `VM_HOST` with value `127.0.0.1` or the Windows host IP + +--- + +## 11. What to capture for submission + +Recommended evidence: + +1. `ansible-playbook playbooks/provision.yml --list-tags` +2. `ansible-playbook playbooks/provision.yml --tags docker_install` +3. First and second `ansible-playbook playbooks/deploy.yml` runs +4. Wipe-only run +5. Clean reinstall run +6. `docker compose ps` output on the VM +7. `curl /health` and `curl /` output +8. GitHub Actions `lint` and `deploy` job logs + +## Overview + +This lab upgrades the Lab05 Ansible project to a more production-ready setup: + +- Refactored roles to use **blocks**, **rescue/always**, and a clear **tag strategy**. +- Migrated the application deployment from `docker run` to **Docker Compose v2**. +- Added **role dependency** (`web_app` depends on `docker`) to guarantee execution order. +- Implemented **safe wipe logic** (double-gated: variable + tag). +- Added a **GitHub Actions** workflow for Ansible linting + deployment. + +Tech used: **Ansible 2.16+**, **community.docker**, **Docker Compose v2**, **GitHub Actions**, **Jinja2**. + +--- + +## Blocks & Tags + +### Tag strategy + +- `common` — the whole common role (set at playbook role level) +- `packages` — package management tasks inside `common` +- `users` — user management tasks inside `common` + +- `docker` — the whole docker role (set at playbook role level) +- `docker_install` — Docker installation + repo setup +- `docker_config` — Docker group/user configuration + +- `web_app` — the whole deployment role (set at playbook role level) +- `app_deploy` — deployment block inside `web_app` +- `compose` — Docker Compose related tasks in `web_app` +- `web_app_wipe` — wipe logic tasks + +### `common` role + +File: `roles/common/tasks/main.yml` + +What was done: + +1. **Packages block** tagged `packages`. +2. **Users block** tagged `users`. +3. Added **rescue** for APT cache update failures: + - best-effort `apt-get update --fix-missing` + - retry cache update. +4. Added an outer **always** section that writes `/tmp/ansible_common_role_completed.log`. + +Notes: + +- User management is **optional** and controlled by `common_users` (default empty list => no changes). +- SSH keys are managed with `ansible.posix.authorized_key` for better collection portability. + +### `docker` role + +File: `roles/docker/tasks/main.yml` + +What was done: + +1. **Install block** tagged `docker_install`. +2. **Config block** tagged `docker_config`. +3. Added **rescue** to handle transient Docker repository / GPG failures: + - wait 10 seconds + - retry `apt update` + - re-download and re-install the GPG key + - retry install. +4. Added an outer **always** section to ensure the Docker service is enabled and started. + +### How to test tags + +```bash +cd ansible + +# List all tags +ansible-playbook playbooks/provision.yml --list-tags + +# Run only docker role tasks +ansible-playbook playbooks/provision.yml --tags docker + +# Run only docker installation tasks +ansible-playbook playbooks/provision.yml --tags docker_install + +# Install packages only +ansible-playbook playbooks/provision.yml --tags packages + +# Skip common role +ansible-playbook playbooks/provision.yml --skip-tags common +``` + +--- + +## Docker Compose Migration + +### Role rename + +The deployment role was renamed from `app_deploy` to `web_app`. +The old `roles/app_deploy/` directory was removed from the final submission. + +### Compose template + +File: `roles/web_app/templates/docker-compose.yml.j2` + +Template supports: + +- `app_name` +- `docker_image` +- `docker_tag` +- `app_port` / `app_internal_port` +- `app_env` (dict of extra env vars) +- optional `app_secret_key` (Vault-friendly) + +It also sets sane defaults for the provided Flask app: + +- `HOST=0.0.0.0` +- `PORT={{ app_internal_port }}` + +### Role dependency + +File: `roles/web_app/meta/main.yml` + +`web_app` depends on `docker`, so a playbook that runs the whole `web_app` role will install Docker first. + +Important practical note: + +- `ansible-playbook playbooks/deploy.yml` is the safest default. +- `ansible-playbook playbooks/deploy.yml --tags web_app` also works for selective role execution. +- `ansible-playbook playbooks/deploy.yml --tags app_deploy` targets only the deployment block and is **not** the best choice if you expect role dependencies to be selected by tag filtering. + +### Deployment implementation + +File: `roles/web_app/tasks/main.yml` + +Deployment flow: + +1. Create `/opt/{{ app_name }}`. +2. Render `docker-compose.yml`. +3. Optional `docker login` (only if password is provided). +4. Pull the image using `community.docker.docker_image`. +5. Run `community.docker.docker_compose_v2` (`state: present`). +6. Verify app responds on `/health` and `/`. + +Idempotency test: + +```bash +ansible-playbook playbooks/deploy.yml +ansible-playbook playbooks/deploy.yml +``` + +The second run should show mostly `ok` tasks, and no container recreation unless inputs changed. + +--- + +## Wipe Logic + +Files: + +- `roles/web_app/defaults/main.yml` +- `roles/web_app/tasks/wipe.yml` +- `roles/web_app/tasks/main.yml` (includes wipe first) + +### Why variable + tag? + +This is a **double safety mechanism**: + +- Variable (`web_app_wipe=true`) prevents accidental wipe during normal runs. +- Tag (`--tags web_app_wipe`) enables a wipe-only run that does not deploy. + +### Test scenarios + +```bash +# 1) Normal deployment (wipe does NOT run) +ansible-playbook playbooks/deploy.yml + +# 2) Wipe only +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe + +# 3) Clean reinstall (wipe -> deploy) +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" + +# 4a) Safety check: tag without variable => wipe skipped +ansible-playbook playbooks/deploy.yml --tags web_app_wipe +``` + +--- + +## CI/CD Integration + +File: `.github/workflows/ansible-deploy.yml` + +### Workflow architecture + +Two-stage pipeline: + +1. **lint** (GitHub-hosted runner) + - installs Ansible + ansible-lint + - installs collections from `ansible/requirements.yml` + - runs `ansible-lint -p .` inside the `ansible/` directory. + +2. **deploy** (self-hosted Linux runner) + - runs on `runs-on: [self-hosted, linux]` + - installs Ansible on the runner + - installs collections from `ansible/requirements.yml` + - runs `ansible-playbook playbooks/deploy.yml` + - verifies the app with `curl /health` and `curl /`. + +### Why a Linux self-hosted runner? + +For a local Vagrant/VirtualBox environment behind NAT, a **Linux self-hosted runner** on the same machine is the simplest free option. +Using WSL2 Ubuntu is practical because: + +- the workflow already uses Bash and Linux paths +- Ansible is easier to maintain on Linux +- no public inbound SSH exposure is required +- it matches the local, free, Windows-friendly lab setup + +### Secrets + +Recommended secrets (Actions → Secrets): + +- `ANSIBLE_VAULT_PASSWORD` — only needed if you use Vault-encrypted files +- `VM_HOST` — host/IP used by the verification curl step (default fallback: `127.0.0.1`) + +If you later move to a public VM and a GitHub-hosted deploy job, add: + +- `SSH_PRIVATE_KEY` +- `VM_USER` + +--- + +## Testing Results Checklist + +Collect evidence for submission: + +- `ansible-playbook playbooks/provision.yml --list-tags` +- `ansible-playbook playbooks/provision.yml --tags docker_install` +- `ansible-playbook playbooks/deploy.yml` +- second deployment run for idempotency proof +- wipe-only run: + - `ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe` +- clean reinstall run: + - `ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true"` +- rendered file: + - `/opt/{{ app_name }}/docker-compose.yml` +- app responses: + - `curl http://:{{ app_port }}/health` + - `curl http://:{{ app_port }}/` +- GitHub Actions logs for `lint` and `deploy` + +--- + +## Challenges & Solutions + +- **Compose v2 on Ubuntu**: the docker role installs `docker-compose-plugin` so `docker compose` is available. +- **Idempotency vs pull policy**: image pulling is done explicitly via `docker_image`, while Compose uses `pull: never`. +- **Safety for destructive operations**: wipe logic requires both a boolean variable and, for wipe-only mode, an explicit tag. +- **Collection portability**: `ansible.posix` was added explicitly so `authorized_key` does not depend on the full `ansible` meta-package. + +--- + +## Research Answers + +### Blocks & Tags + +1. **What happens if the rescue block also fails?** + - The whole block is considered failed and Ansible stops the play (unless errors are ignored). + - The `always` section still runs. + +2. **Can you have nested blocks?** + - Yes. This lab uses an outer block with an `always` section, and inner blocks for `packages` / `users`. + +3. **How do tags inherit to tasks within blocks?** + - Tags defined at a block level are applied to all tasks in that block. + - Tags defined at role level (in playbooks) also apply to all tasks in the role. + +### Docker Compose + +1. **Difference between `restart: always` and `restart: unless-stopped`?** + - `always` restarts containers even if you manually stopped them. + - `unless-stopped` restarts containers automatically except when they were explicitly stopped by an operator. + +2. **How do Compose networks differ from Docker bridge networks?** + - Compose creates and manages networks as part of an application project, with predictable naming. + - A classic Docker bridge network is a lower-level primitive not tied to a Compose project. + +3. **Can you reference Ansible Vault variables in the template?** + - Yes. Vault-encrypted variables are decrypted at runtime and can be used like normal variables in templates. + +### Wipe logic + +1. **Why use both variable AND tag?** + - The variable protects normal runs. + - The tag enables a wipe-only execution that skips deployment tasks. + +2. **Difference between `never` tag and this approach?** + - `never` requires explicit `--tags never` (or another selected tag on the same task) and can be confusing. + - Variable + tag is explicit and easier to reason about for safety-critical operations. + +3. **Why must wipe logic come BEFORE deployment?** + - It enables the clean reinstall flow: remove old state first, then deploy from scratch. + +4. **When do you want clean reinstall vs rolling update?** + - Clean reinstall: broken state, changed config/layout, major upgrades, testing fresh installs. + - Rolling update: keep service available, minimize downtime, gradual rollout. + +5. **How to extend wipe to remove images/volumes too?** + - Add Compose options such as `remove_volumes: true` when needed. + - Remove images via `community.docker.docker_image state: absent`. + - Remove named volumes explicitly with Docker modules or CLI commands. + +### CI/CD + +1. **Security implications of storing SSH keys in GitHub Secrets?** + - Secrets can be exfiltrated if workflows are compromised. + - Limit key scope, use dedicated deploy keys, restrict repo access, and rotate regularly. + +2. **How to implement staging → production pipeline?** + - Use separate environments, separate inventories, and manual approvals for production. + - Another option is separate workflows triggered on tags/releases. + +3. **What to add for rollbacks?** + - Pin image tags, keep the previous known-good tag, and add a rollback job that redeploys it. + +4. **How does a self-hosted runner improve security compared to GitHub-hosted?** + - SSH keys/secrets do not need to be sent to GitHub-hosted machines. + - The runner stays inside your controlled network perimeter. + diff --git a/ansible/group_vars/all.yml.example b/ansible/group_vars/all.yml.example index 2dbc32167c..e82ce0e542 100644 --- a/ansible/group_vars/all.yml.example +++ b/ansible/group_vars/all.yml.example @@ -1,13 +1,17 @@ --- -# Пример! Настоящие значения храните в ЗАШИФРОВАННОМ файле: +# Example only. Store real values in an encrypted file, for example: # ansible-vault create ansible/group_vars/all.yml dockerhub_username: "CHANGE_ME" -dockerhub_password: "CHANGE_ME" # лучше токен, не пароль +dockerhub_password: "CHANGE_ME" # Prefer a token instead of a password app_name: "devops-info-service" docker_image: "{{ dockerhub_username }}/{{ app_name }}" -docker_image_tag: "latest" +docker_tag: "latest" -app_port: 5000 -app_container_name: "{{ app_name }}" +# Course default is 8000, but you can change it if your lab setup requires it. +app_port: 8000 +app_internal_port: 8000 + +# Extra environment variables for the container (dict) +app_env: {} diff --git a/ansible/group_vars/webservers.yml b/ansible/group_vars/webservers.yml index cd5ba022fb..310cc192e1 100644 --- a/ansible/group_vars/webservers.yml +++ b/ansible/group_vars/webservers.yml @@ -2,9 +2,15 @@ dockerhub_username: "dorley174" dockerhub_password: "" +# Application / image app_name: "devops-info-service" docker_image: "{{ dockerhub_username }}/{{ app_name }}" -docker_image_tag: "latest" +docker_tag: "latest" -app_port: 5000 -app_container_name: "{{ app_name }}" +# Expose the app on the course-standard port. +app_port: 8000 +app_internal_port: 8000 + +# Extra environment variables for the container (optional) +app_env: {} +dockerhub_username: dorley174 diff --git a/ansible/inventory/group_vars/webservers.yml b/ansible/inventory/group_vars/webservers.yml new file mode 100644 index 0000000000..2d245cbfeb --- /dev/null +++ b/ansible/inventory/group_vars/webservers.yml @@ -0,0 +1,15 @@ +--- +dockerhub_username: "dorley174" +dockerhub_password: "" + +# Application / image +app_name: "devops-info-service" +docker_image: "{{ dockerhub_username }}/{{ app_name }}" +docker_tag: "latest" + +# Expose the app on the course-standard port. +app_port: 8000 +app_internal_port: 8000 + +# Extra environment variables for the container (optional) +app_env: {} diff --git a/ansible/inventory/hosts.ini b/ansible/inventory/hosts.ini index 94c50c5212..d521f32a26 100644 --- a/ansible/inventory/hosts.ini +++ b/ansible/inventory/hosts.ini @@ -1,19 +1,5 @@ -# Option A (cloud VM from Lab04): -# web1 ansible_host= ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_ed25519 - -# Option B (100% free, recommended for Windows 11): Vagrant + VirtualBox target VM -# 1) Run `vagrant up` on Windows (PowerShell) in the repo root. -# 2) Check forwarded ports: `vagrant port`. -# 3) In WSL, get Windows host IP: -# WIN_HOST_IP=$(grep -m1 nameserver /etc/resolv.conf | awk '{print $2}') -# 4) Copy Vagrant private key from the project to Linux home and chmod 600. -# 5) Put the detected values below: -# vagrant1 ansible_host= ansible_port= ansible_user=vagrant ansible_ssh_private_key_file=~/.ssh/lab05_vagrant_key - -# Option C (local-only): run on localhost (WSL acts as a "server") -# localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3 [webservers] -vagrant1 ansible_host=192.168.31.32 ansible_port=2222 ansible_user=vagrant ansible_ssh_private_key_file=~/.ssh/lab05_vagrant_key +vagrant1 ansible_host=172.19.144.1 ansible_port=2222 ansible_user=vagrant ansible_ssh_private_key_file=/home/dorley/.ssh/lab05_vagrant_key [webservers:vars] ansible_python_interpreter=/usr/bin/python3 diff --git a/ansible/playbooks/deploy.yml b/ansible/playbooks/deploy.yml index af1b388e3f..f9c3864e66 100644 --- a/ansible/playbooks/deploy.yml +++ b/ansible/playbooks/deploy.yml @@ -3,8 +3,8 @@ hosts: webservers become: true - vars_files: - - ../group_vars/webservers.yml - roles: - - app_deploy + # Docker is a dependency of web_app (see roles/web_app/meta/main.yml) + - role: web_app + tags: + - web_app diff --git a/ansible/playbooks/provision.yml b/ansible/playbooks/provision.yml index 7cc2e6678d..9f6954005f 100644 --- a/ansible/playbooks/provision.yml +++ b/ansible/playbooks/provision.yml @@ -4,5 +4,10 @@ become: true roles: - - common - - docker + - role: common + tags: + - common + + - role: docker + tags: + - docker diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml index ae8e6f8951..6606e5aea3 100644 --- a/ansible/playbooks/site.yml +++ b/ansible/playbooks/site.yml @@ -4,6 +4,11 @@ become: true roles: - - common - - docker - - app_deploy + - role: common + tags: + - common + + # Docker role is executed automatically as a dependency of web_app. + - role: web_app + tags: + - web_app diff --git a/ansible/requirements.yml b/ansible/requirements.yml index b869f415df..ff73cd7f28 100644 --- a/ansible/requirements.yml +++ b/ansible/requirements.yml @@ -2,3 +2,4 @@ collections: - name: community.docker - name: community.general + - name: ansible.posix diff --git a/ansible/roles/app_deploy/defaults/main.yml b/ansible/roles/app_deploy/defaults/main.yml deleted file mode 100644 index 86a10efbf9..0000000000 --- a/ansible/roles/app_deploy/defaults/main.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# App defaults (can be overridden from group_vars/all.yml vault file) -app_name: "devops-info-service" -app_container_name: "{{ app_name }}" - -docker_image_tag: "latest" - -# Host->container port mapping -app_port: 5000 -container_port: 5000 - -# Docker container restart policy -app_restart_policy: "unless-stopped" - -# Environment variables passed to container (dict) -app_env: {} - -# Optional registry URL (empty means Docker Hub) -# Example: "cr.yandex/xxx" or "ghcr.io" -docker_registry: "" diff --git a/ansible/roles/app_deploy/handlers/main.yml b/ansible/roles/app_deploy/handlers/main.yml deleted file mode 100644 index 1fc3fba48b..0000000000 --- a/ansible/roles/app_deploy/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: restart app container - community.docker.docker_container: - name: "{{ app_container_name }}" - state: started - restart: true diff --git a/ansible/roles/app_deploy/tasks/main.yml b/ansible/roles/app_deploy/tasks/main.yml deleted file mode 100644 index 934817c0c3..0000000000 --- a/ansible/roles/app_deploy/tasks/main.yml +++ /dev/null @@ -1,77 +0,0 @@ ---- -- name: Ensure Docker SDK for Python is installed on target - ansible.builtin.apt: - name: python3-docker - state: present - update_cache: true - -- name: Login to Docker registry - community.docker.docker_login: - registry_url: "{{ (docker_registry | length > 0) | ternary(docker_registry, omit) }}" - username: "{{ dockerhub_username }}" - password: "{{ dockerhub_password }}" - no_log: true - when: dockerhub_password is defined and dockerhub_password | length > 0 - -- name: Pull application image - community.docker.docker_image: - name: "{{ (docker_image | default(dockerhub_username ~ '/' ~ app_name)) }}:{{ (docker_image_tag | default('latest')) }}" - source: pull - register: app_image_pull - -- name: Check current container (if exists) - community.docker.docker_container_info: - name: "{{ app_container_name }}" - register: app_container_info - failed_when: false - -- name: Decide if redeploy is needed - ansible.builtin.set_fact: - app_needs_redeploy: "{{ app_image_pull.changed or (not app_container_info.exists) }}" - -- name: Stop existing container (only if redeploy needed) - community.docker.docker_container: - name: "{{ app_container_name }}" - state: stopped - when: app_container_info.exists and app_needs_redeploy - -- name: Remove existing container (only if redeploy needed) - community.docker.docker_container: - name: "{{ app_container_name }}" - state: absent - when: app_container_info.exists and app_needs_redeploy - -- name: Run application container - community.docker.docker_container: - name: "{{ app_container_name }}" - image: "{{ (docker_image | default(dockerhub_username ~ '/' ~ app_name)) }}:{{ (docker_image_tag | default('latest')) }}" - state: started - restart_policy: "{{ app_restart_policy }}" - published_ports: - - "{{ app_port }}:{{ container_port }}" - env: "{{ app_env }}" - notify: restart app container - -- name: Wait for the application port to become available - ansible.builtin.wait_for: - host: "127.0.0.1" - port: "{{ app_port }}" - timeout: 60 - -- name: Health check (/health) - ansible.builtin.uri: - url: "http://127.0.0.1:{{ app_port }}/health" - method: GET - status_code: 200 - return_content: true - register: app_health - retries: 10 - delay: 3 - until: app_health.status == 200 - -- name: Verify main endpoint (/) - ansible.builtin.uri: - url: "http://127.0.0.1:{{ app_port }}/" - method: GET - status_code: 200 - return_content: false diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml index 9864c34cc2..f7648321e4 100644 --- a/ansible/roles/common/defaults/main.yml +++ b/ansible/roles/common/defaults/main.yml @@ -15,6 +15,21 @@ common_packages: # Default timezone (change if needed) common_timezone: "Europe/Moscow" -# В WSL и некоторых окружениях timedatectl может быть недоступен. -# По умолчанию timezone НЕ меняем. +# In WSL and some minimal environments, timedatectl may be unavailable. +# By default, timezone changes are disabled. common_set_timezone: false + +# --------------------------------------------------------------------------- +# User management (optional) +# --------------------------------------------------------------------------- +# Lab06 requires a "users" block. To avoid unexpected changes on shared hosts, +# this is disabled by default (empty list => no-op). +# +# Example: +# common_users: +# - name: devops +# groups: sudo +# sudo_nopasswd: true +# authorized_keys: +# - "ssh-ed25519 AAAA... your_key" +common_users: [] diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index a2464dd655..514aa4e705 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -1,15 +1,101 @@ --- -- name: Update apt cache - ansible.builtin.apt: - update_cache: true - cache_valid_time: 3600 - -- name: Install common packages - ansible.builtin.apt: - name: "{{ common_packages }}" - state: present - -- name: Set timezone - community.general.timezone: - name: "{{ common_timezone }}" - when: common_set_timezone | bool + +# This role demonstrates advanced Ansible features (Lab06): +# - nested blocks +# - rescue/always error handling +# - tag strategy for selective execution + +- name: Common role tasks + block: + # ----------------------------------------------------------------------- + # Packages + # ----------------------------------------------------------------------- + - name: Install common packages + block: + - name: Update apt cache + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + - name: Install common packages + ansible.builtin.apt: + name: "{{ common_packages }}" + state: present + + rescue: + # NOTE: If this rescue block fails too, Ansible will stop the play. + # The outer 'always' section will still run. + - name: Fix APT metadata issues (best-effort) # noqa command-instead-of-module + ansible.builtin.command: apt-get update --fix-missing + changed_when: false + + - name: Retry apt cache update + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + become: true + tags: + - packages + + # ----------------------------------------------------------------------- + # Users + # ----------------------------------------------------------------------- + - name: Manage users (optional) + block: + - name: Ensure requested users exist + ansible.builtin.user: + name: "{{ item.name }}" + groups: "{{ item.groups | default(omit) }}" + shell: "{{ item.shell | default('/bin/bash') }}" + create_home: true + state: present + loop: "{{ common_users }}" + loop_control: + label: "{{ item.name }}" + + - name: Configure passwordless sudo (optional) + ansible.builtin.copy: + dest: "/etc/sudoers.d/{{ item.name }}" + content: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL\n" + mode: "0440" + validate: "visudo -cf %s" + when: item.sudo_nopasswd | default(false) | bool + loop: "{{ common_users }}" + loop_control: + label: "{{ item.name }}" + + - name: Add SSH authorized keys (optional) + ansible.posix.authorized_key: + user: "{{ item.0.name }}" + key: "{{ item.1 }}" + state: present + loop: "{{ common_users | subelements('authorized_keys', skip_missing=True) }}" + loop_control: + label: "{{ item.0.name }}" + + become: true + tags: + - users + + # ----------------------------------------------------------------------- + # Other system settings + # ----------------------------------------------------------------------- + - name: Set timezone + community.general.timezone: + name: "{{ common_timezone }}" + when: common_set_timezone | bool + become: true + + always: + # A simple completion marker that can be used as "evidence". + - name: Log common role completion + ansible.builtin.copy: + dest: /tmp/ansible_common_role_completed.log + content: "common role completed at {{ ansible_date_time.iso8601 }}\n" + mode: "0644" + become: true + changed_when: false + tags: + - packages + - users diff --git a/ansible/roles/docker/handlers/main.yml b/ansible/roles/docker/handlers/main.yml index 1a5058da5e..0162ba52da 100644 --- a/ansible/roles/docker/handlers/main.yml +++ b/ansible/roles/docker/handlers/main.yml @@ -1,5 +1,5 @@ --- -- name: restart docker +- name: Restart Docker ansible.builtin.service: name: docker state: restarted diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index b98c9a8450..b6734814c6 100644 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -1,77 +1,155 @@ --- -- name: Install prerequisites for Docker repository - ansible.builtin.apt: - name: - - ca-certificates - - curl - - gnupg - state: present - update_cache: true - -- name: Ensure /etc/apt/keyrings exists - ansible.builtin.file: - path: /etc/apt/keyrings - state: directory - mode: "0755" - -- name: Download Docker GPG key (ASCII) - ansible.builtin.get_url: - url: "{{ docker_gpg_key_url }}" - dest: /tmp/docker.gpg - mode: "0644" - register: docker_gpg_download - -- name: Check if Docker keyring already exists - ansible.builtin.stat: - path: "{{ docker_keyring_path }}" - register: docker_keyring_stat - -- name: Convert (dearmor) Docker GPG key to keyring - ansible.builtin.command: - cmd: "gpg --dearmor -o {{ docker_keyring_path }} /tmp/docker.gpg" - when: docker_gpg_download.changed or (not docker_keyring_stat.stat.exists) - notify: restart docker - -- name: Set correct permissions on Docker keyring - ansible.builtin.file: - path: "{{ docker_keyring_path }}" - mode: "0644" - -- name: Set Docker APT architecture mapping - ansible.builtin.set_fact: - docker_apt_arch: "{{ {'x86_64':'amd64','aarch64':'arm64'}.get(ansible_architecture, ansible_architecture) }}" - -- name: Add official Docker APT repository - ansible.builtin.apt_repository: - repo: "deb [arch={{ docker_apt_arch }} signed-by={{ docker_keyring_path }}] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" - state: present - filename: "{{ docker_repo_filename }}" - -- name: Install Docker Engine packages - ansible.builtin.apt: - name: "{{ docker_packages }}" - state: present - update_cache: true - notify: restart docker - -- name: Ensure Docker service is enabled and running - ansible.builtin.service: - name: docker - state: started - enabled: true - -- name: Ensure docker group exists - ansible.builtin.group: - name: docker - state: present - -- name: Add user to docker group - ansible.builtin.user: - name: "{{ docker_user }}" - groups: docker - append: true - -- name: Install Docker SDK for Python on target (for Ansible docker modules) - ansible.builtin.apt: - name: python3-docker - state: present + +# Lab06: this role is refactored to use blocks, tags and error handling. +# Tags: +# - docker (role-level tag, set in playbooks) +# - docker_install (installation only) +# - docker_config (configuration only) + +- name: Docker role tasks + block: + # ----------------------------------------------------------------------- + # Installation + # ----------------------------------------------------------------------- + - name: Install Docker Engine + block: + - name: Install prerequisites for Docker repository + ansible.builtin.apt: + name: + - ca-certificates + - curl + - gnupg + state: present + update_cache: true + + - name: Ensure /etc/apt/keyrings exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + + - name: Download Docker GPG key (ASCII) + ansible.builtin.get_url: + url: "{{ docker_gpg_key_url }}" + dest: /tmp/docker.gpg + mode: "0644" + register: docker_gpg_download + + - name: Check if Docker keyring already exists + ansible.builtin.stat: + path: "{{ docker_keyring_path }}" + register: docker_keyring_stat + + - name: Convert (dearmor) Docker GPG key to keyring + ansible.builtin.command: + cmd: "gpg --dearmor -o {{ docker_keyring_path }} /tmp/docker.gpg" + when: docker_gpg_download.changed or (not docker_keyring_stat.stat.exists) + changed_when: true + notify: Restart Docker + + - name: Set correct permissions on Docker keyring + ansible.builtin.file: + path: "{{ docker_keyring_path }}" + mode: "0644" + + - name: Set Docker APT architecture mapping + ansible.builtin.set_fact: + docker_apt_arch: "{{ {'x86_64': 'amd64', 'aarch64': 'arm64'}.get(ansible_architecture, ansible_architecture) }}" + + - name: Add official Docker APT repository + ansible.builtin.apt_repository: + repo: >- + deb [arch={{ docker_apt_arch }} signed-by={{ docker_keyring_path }}] + https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable + state: present + filename: "{{ docker_repo_filename }}" + + - name: Install Docker Engine packages + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + update_cache: true + notify: Restart Docker + + - name: Install Docker SDK for Python on target (for Ansible docker modules) + ansible.builtin.apt: + name: python3-docker + state: present + + rescue: + # Docker repository setup can occasionally fail (GPG/network hiccups). + # We do a best-effort retry after a short wait. + - name: Wait 10 seconds before retrying Docker repository setup + ansible.builtin.pause: + seconds: 10 + + - name: Retry apt cache update + ansible.builtin.apt: + update_cache: true + + - name: Re-download Docker GPG key (force) + ansible.builtin.get_url: + url: "{{ docker_gpg_key_url }}" + dest: /tmp/docker.gpg + mode: "0644" + force: true + + - name: Retry converting (dearmor) Docker GPG key + ansible.builtin.command: + cmd: "gpg --dearmor -o {{ docker_keyring_path }} /tmp/docker.gpg" + changed_when: true + notify: Restart Docker + + - name: Retry adding Docker APT repository + ansible.builtin.apt_repository: + repo: >- + deb [arch={{ docker_apt_arch | default('amd64') }} signed-by={{ docker_keyring_path }}] + https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable + state: present + filename: "{{ docker_repo_filename }}" + + - name: Retry installing Docker Engine packages + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + update_cache: true + notify: Restart Docker + + become: true + tags: + - docker_install + + # ----------------------------------------------------------------------- + # Configuration + # ----------------------------------------------------------------------- + - name: Configure Docker + block: + - name: Ensure docker group exists + ansible.builtin.group: + name: docker + state: present + + - name: Add user to docker group + ansible.builtin.user: + name: "{{ docker_user }}" + groups: docker + append: true + + become: true + tags: + - docker_config + + always: + # Lab06 requirement: always ensure Docker service is enabled & started. + # If Docker is not installed yet, we do not fail the whole play. + - name: Ensure Docker service is enabled and running + ansible.builtin.service: + name: docker + state: started + enabled: true + become: true + register: docker_service_result + failed_when: false + tags: + - docker_install + - docker_config diff --git a/ansible/roles/web_app/defaults/main.yml b/ansible/roles/web_app/defaults/main.yml new file mode 100644 index 0000000000..57d9c2ea83 --- /dev/null +++ b/ansible/roles/web_app/defaults/main.yml @@ -0,0 +1,45 @@ +--- + +# --------------------------------------------------------------------------- +# Web application deployment (Docker Compose) +# --------------------------------------------------------------------------- + +# Service/container name +app_name: devops-app + +# Docker image reference (repository part, without tag) +# Example: "your_dockerhub_username/devops-info-service" +docker_image: "{{ (dockerhub_username is defined and dockerhub_username | length > 0) | ternary(dockerhub_username ~ '/' ~ app_name, app_name) }}" + +# Docker image tag/version +# Backward compatible with Lab05 variable name `docker_image_tag`. +docker_tag: "{{ docker_image_tag | default('latest') }}" + +# Host -> container port mapping +app_port: 8000 + +# Internal container port. Backward compatible with Lab05 variable `container_port`. +app_internal_port: "{{ container_port | default(8000) }}" + +# Where docker-compose.yml will be stored on the target host +compose_project_dir: "/opt/{{ app_name }}" + +# Compose file version header (mainly for readability; Compose v2 doesn't require it) +docker_compose_version: "3.8" + +# Extra environment variables passed to the container (merged in the template) +app_env: {} + +# Optional registry URL (empty means Docker Hub) +docker_registry: "" + +# --------------------------------------------------------------------------- +# Wipe Logic (Lab06 Task 3) +# --------------------------------------------------------------------------- +# Double-gated safety: +# 1) variable web_app_wipe=true +# 2) tag web_app_wipe (for wipe-only runs) +web_app_wipe: false + +# Optional: remove the image during wipe (useful to save disk space) +web_app_wipe_remove_image: false diff --git a/ansible/roles/web_app/meta/main.yml b/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..6ab81a8714 --- /dev/null +++ b/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,7 @@ +--- + +# Role dependencies ensure correct execution order. +# Lab06 requirement: Docker must be installed before deploying the web app. + +dependencies: + - role: docker diff --git a/ansible/roles/web_app/tasks/main.yml b/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..a173d708f8 --- /dev/null +++ b/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,89 @@ +--- + +# Lab06 Task 3: wipe logic runs first (only when variable is true). +- name: Include wipe tasks + ansible.builtin.include_tasks: wipe.yml + tags: + - web_app_wipe + + +# Lab06 Task 2: deploy with Docker Compose v2. +- name: Deploy application with Docker Compose + block: + - name: Create application directory + ansible.builtin.file: + path: "{{ compose_project_dir }}" + state: directory + owner: root + group: root + mode: "0755" + + - name: Template docker-compose.yml + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ compose_project_dir }}/docker-compose.yml" + mode: "0644" + + # Optional: if the image is private, login must be done on the target host. + - name: Login to Docker registry (optional) + community.docker.docker_login: + registry_url: "{{ (docker_registry | length > 0) | ternary(docker_registry, omit) }}" + username: "{{ dockerhub_username }}" + password: "{{ dockerhub_password }}" + no_log: true + when: + - dockerhub_username is defined + - dockerhub_password is defined + - dockerhub_password | length > 0 + + # Keep idempotency: pull only if image changed. + - name: Pull application image + community.docker.docker_image: + name: "{{ docker_image }}:{{ docker_tag }}" + source: pull + register: web_app_image_pull + + - name: Deploy (docker compose up) + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: present + pull: never + recreate: auto + register: web_app_compose + + - name: Wait for the application port to become available + ansible.builtin.wait_for: + host: "127.0.0.1" + port: "{{ app_port }}" + timeout: 60 + + - name: Health check (/health) + ansible.builtin.uri: + url: "http://127.0.0.1:{{ app_port }}/health" + method: GET + status_code: 200 + return_content: true + register: web_app_health + retries: 10 + delay: 3 + until: web_app_health.status == 200 + + - name: Verify main endpoint (/) + ansible.builtin.uri: + url: "http://127.0.0.1:{{ app_port }}/" + method: GET + status_code: 200 + return_content: false + + rescue: + - name: Deployment failure hint + ansible.builtin.debug: + msg: | + Web app deployment failed. + Try checking logs on the target host: + cd {{ compose_project_dir }} + docker compose logs --no-color --tail=200 + + tags: + - app_deploy + - compose diff --git a/ansible/roles/web_app/tasks/wipe.yml b/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..df8a348e82 --- /dev/null +++ b/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,55 @@ +--- + +# Lab06 Task 3: Safe wipe logic. +# Double-gating: +# 1) web_app_wipe=true +# 2) tag web_app_wipe (for wipe-only runs) + +- name: Wipe web application + block: + - name: Check if app directory exists + ansible.builtin.stat: + path: "{{ compose_project_dir }}" + register: web_app_dir + + - name: Stop and remove containers (docker compose down) + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: absent + remove_orphans: true + when: web_app_dir.stat.exists + register: web_app_compose_down + failed_when: false + + - name: Remove docker-compose.yml + ansible.builtin.file: + path: "{{ compose_project_dir }}/docker-compose.yml" + state: absent + + - name: Remove application directory + ansible.builtin.file: + path: "{{ compose_project_dir }}" + state: absent + + - name: Remove application image (optional) + community.docker.docker_image: + name: "{{ docker_image }}:{{ docker_tag }}" + state: absent + when: web_app_wipe_remove_image | bool + register: web_app_image_remove + failed_when: false + + - name: Log wipe completion + ansible.builtin.copy: + dest: /tmp/ansible_web_app_wipe.log + content: "web app {{ app_name }} wiped at {{ ansible_date_time.iso8601 }}\n" + mode: "0644" + changed_when: false + + - name: Wipe summary + ansible.builtin.debug: + msg: "Application {{ app_name }} wiped successfully" + + when: web_app_wipe | bool + tags: + - web_app_wipe diff --git a/ansible/roles/web_app/templates/docker-compose.yml.j2 b/ansible/roles/web_app/templates/docker-compose.yml.j2 new file mode 100644 index 0000000000..33a7107ab5 --- /dev/null +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,27 @@ +version: '{{ docker_compose_version }}' + +services: + {{ app_name }}: + image: {{ docker_image }}:{{ docker_tag }} + container_name: {{ app_name }} + ports: + - "{{ app_port }}:{{ app_internal_port }}" + environment: + # Defaults to make the provided Flask app listen on the expected port. + HOST: "0.0.0.0" + PORT: "{{ app_internal_port }}" +{%- if app_env is defined and app_env | length > 0 %} +{%- for key, value in app_env.items() %} + {{ key }}: {{ value | quote }} +{%- endfor %} +{%- endif %} +{%- if app_secret_key is defined %} + APP_SECRET_KEY: {{ app_secret_key | quote }} +{%- endif %} + restart: unless-stopped + networks: + - {{ app_name }}_net + +networks: + {{ app_name }}_net: + driver: bridge From 2a8bf65619068010c4471523d5e59f48a63ff0ad Mon Sep 17 00:00:00 2001 From: dorley174 Date: Thu, 12 Mar 2026 19:16:43 +0300 Subject: [PATCH 3/5] added lab06 overview --- ansible/docs/{kab07_overview.md => lab06_overview.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ansible/docs/{kab07_overview.md => lab06_overview.md} (100%) diff --git a/ansible/docs/kab07_overview.md b/ansible/docs/lab06_overview.md similarity index 100% rename from ansible/docs/kab07_overview.md rename to ansible/docs/lab06_overview.md From b43818c3f718e0762466eb13b6fbe5fa89b6af0f Mon Sep 17 00:00:00 2001 From: dorley174 Date: Thu, 12 Mar 2026 19:48:43 +0300 Subject: [PATCH 4/5] fixing ci 1 --- .github/workflows/ansible-deploy.yml | 24 +++++++++++------------- .gitignore | 3 +++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ansible-deploy.yml b/.github/workflows/ansible-deploy.yml index b2713dc4c9..278b8c3e3a 100644 --- a/.github/workflows/ansible-deploy.yml +++ b/.github/workflows/ansible-deploy.yml @@ -44,30 +44,26 @@ jobs: name: Deploy Application needs: lint - # For a local Vagrant/VirtualBox setup behind NAT, the simplest free option - # is a self-hosted Linux runner on the same machine (for example, inside WSL2). runs-on: [self-hosted, linux] steps: - name: Checkout uses: actions/checkout@v4 - - name: Install Ansible (self-hosted) + - name: Set up Python venv shell: bash run: | set -euo pipefail - python3 -m pip install --user --upgrade pip - python3 -m pip install --user ansible - - - name: Add user-local bin to PATH - shell: bash - run: | - echo "$HOME/.local/bin" >> "$GITHUB_PATH" + python3 -m venv .venv + . .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install ansible ansible-lint - name: Install Ansible collections shell: bash run: | set -euo pipefail + . .venv/bin/activate ansible-galaxy collection install -r ansible/requirements.yml - name: Deploy with Ansible @@ -76,14 +72,16 @@ jobs: ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }} run: | set -euo pipefail + . .venv/bin/activate + export ANSIBLE_CONFIG="$GITHUB_WORKSPACE/ansible/ansible.cfg" cd ansible if [[ -n "${ANSIBLE_VAULT_PASSWORD:-}" ]]; then trap 'rm -f /tmp/vault_pass' EXIT printf '%s' "${ANSIBLE_VAULT_PASSWORD}" > /tmp/vault_pass - ansible-playbook playbooks/deploy.yml --vault-password-file /tmp/vault_pass + ansible-playbook -i inventory/hosts.ini playbooks/deploy.yml --vault-password-file /tmp/vault_pass else - ansible-playbook playbooks/deploy.yml + ansible-playbook -i inventory/hosts.ini playbooks/deploy.yml fi - name: Verify deployment @@ -94,4 +92,4 @@ jobs: set -euo pipefail HOST="${VM_HOST:-127.0.0.1}" curl --retry 10 --retry-delay 3 --retry-connrefused -fsS "http://${HOST}:8000/health" - curl --retry 10 --retry-delay 3 --retry-connrefused -fsS "http://${HOST}:8000/" + curl --retry 10 --retry-delay 3 --retry-connrefused -fsS "http://${HOST}:8000/" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3ae73a858e..45f14ccf09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ test .env .venv +actions-runner/ +_work/ +.runner/ key.json # --- Ansible --- *.retry From 852593c3cf218395ab467d5b3f6154959b265fb3 Mon Sep 17 00:00:00 2001 From: dorley174 Date: Thu, 12 Mar 2026 19:59:39 +0300 Subject: [PATCH 5/5] fixing ci 2 --- ansible/0 | 0 ansible/docs/LAB06.md | 375 ++++++++++++++---- ansible/docs/LOCAL_VALIDATION_WINDOWS.md | 282 +++++++++++++ .../web_app/templates/docker-compose.yml.j2 | 19 +- 4 files changed, 580 insertions(+), 96 deletions(-) create mode 100644 ansible/0 create mode 100644 ansible/docs/LOCAL_VALIDATION_WINDOWS.md diff --git a/ansible/0 b/ansible/0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/docs/LAB06.md b/ansible/docs/LAB06.md index 5374f4a080..c851f78b3c 100644 --- a/ansible/docs/LAB06.md +++ b/ansible/docs/LAB06.md @@ -1,120 +1,323 @@ -# Lab 06 — Ansible Roles Refactoring and Local Deployment Validation +# LAB06 — Advanced Ansible & CI/CD -## Summary +## Overview -This lab was implemented and validated in a fully local and free environment on Windows 11 using VirtualBox, Vagrant, WSL2 Ubuntu, Ansible, and Docker. +This lab upgrades the Lab05 Ansible project to a more production-ready setup: -The infrastructure provisioning flow works correctly, static validation passes, and the deployment playbook reaches the final Docker Compose stage. The remaining issue is a Docker Compose YAML rendering error in the generated `docker-compose.yml`, which prevents the application from being started successfully. +- Refactored roles to use **blocks**, **rescue/always**, and a clear **tag strategy**. +- Migrated the application deployment from `docker run` to **Docker Compose v2**. +- Added **role dependency** (`web_app` depends on `docker`) to guarantee execution order. +- Implemented **safe wipe logic** (double-gated: variable + tag). +- Added a **GitHub Actions** workflow for Ansible linting + deployment. -## Environment +Tech used: **Ansible 2.16+**, **community.docker**, **Docker Compose v2**, **GitHub Actions**, **Jinja2**. -- Host OS: Windows 11 -- Virtualization: VirtualBox -- VM orchestration: Vagrant -- Automation runtime: WSL2 Ubuntu -- Configuration management: Ansible -- Container runtime: Docker Engine + Docker Compose plugin -- CI approach: prepared for self-hosted Linux runner -- Validation model: fully local, no paid cloud resources used +--- -## Implemented Changes +## Blocks & Tags -### Task 1 — Refactor existing roles with `block`, `rescue`, and `always` -- Refactored `common` role to use structured error handling blocks. -- Refactored `docker` role to use structured error handling blocks. -- Added meaningful tags to role tasks. +### Tag strategy -### Task 2 — Create a deploy role based on Docker Compose -- Replaced the old deployment logic with a `web_app` role. -- Added Docker dependency through role metadata. -- Implemented application deployment using Docker Compose v2. -- Added optional wipe logic with dedicated tags and safety checks. +- `common` — the whole common role (set at playbook role level) +- `packages` — package management tasks inside `common` +- `users` — user management tasks inside `common` -### Task 3 — Add selective wipe support -- Implemented wipe-only workflow using `web_app_wipe` tags. -- Added safety condition to prevent accidental destructive cleanup. +- `docker` — the whole docker role (set at playbook role level) +- `docker_install` — Docker installation + repo setup +- `docker_config` — Docker group/user configuration -### Task 4 — Prepare local free execution model -- Configured the project to work against a Vagrant VM. -- Validated SSH access from WSL2 to the VM through forwarded port `2222`. -- Used Windows host IP from WSL2 (`172.19.144.1`) for Ansible connectivity. +- `web_app` — the whole deployment role (set at playbook role level) +- `app_deploy` — deployment block inside `web_app` +- `compose` — Docker Compose related tasks in `web_app` +- `web_app_wipe` — wipe logic tasks -### Task 5 — Documentation -- Added local validation instructions. -- Prepared reproducible local execution steps for Windows + WSL2. +### `common` role -## Validation Results +File: `roles/common/tasks/main.yml` -### Static Validation -The following checks completed successfully: +What was done: -- `ansible all -i inventory/hosts.ini -m ping` -- `ansible-playbook -i inventory/hosts.ini playbooks/provision.yml --syntax-check` -- `ansible-playbook -i inventory/hosts.ini playbooks/deploy.yml --syntax-check` -- `ansible-lint --format=pep8 playbooks/*.yml roles` +1. **Packages block** tagged `packages`. +2. **Users block** tagged `users`. +3. Added **rescue** for APT cache update failures: + - best-effort `apt-get update --fix-missing` + - retry cache update. +4. Added an outer **always** section that writes `/tmp/ansible_common_role_completed.log`. -### Provisioning Validation -`playbooks/provision.yml` completed successfully. +Notes: -Validated outcomes: -- base packages installed -- Docker repository configured -- Docker Engine installed -- Docker service enabled and running -- target host reachable and manageable by Ansible +- User management is **optional** and controlled by `common_users` (default empty list => no changes). +- SSH keys are managed with `ansible.posix.authorized_key` for better collection portability. -### Deployment Validation -`playbooks/deploy.yml` was executed successfully up to the application deployment stage. +### `docker` role -Validated outcomes: -- Docker role dependency executed correctly -- application directory was created -- `docker-compose.yml` was rendered and copied to target host -- Docker image reference was resolved correctly -- image pull from Docker Hub succeeded after transient network timeout recovery +File: `roles/docker/tasks/main.yml` -### Final Blocking Issue -The deployment currently stops at the Docker Compose execution stage with the following error: +What was done: -`yaml: line 12, column 30: mapping values are not allowed in this context` +1. **Install block** tagged `docker_install`. +2. **Config block** tagged `docker_config`. +3. Added **rescue** to handle transient Docker repository / GPG failures: + - wait 10 seconds + - retry `apt update` + - re-download and re-install the GPG key + - retry install. +4. Added an outer **always** section to ensure the Docker service is enabled and started. -This indicates that the generated `docker-compose.yml` still contains a YAML formatting issue and requires one final template fix before the application can be started successfully. +### How to test tags -## Issues Encountered During Validation +```bash +cd ansible -### 1. WSL2 to localhost forwarding -WSL2 could not reach the forwarded SSH port on `127.0.0.1:2222`. -This was resolved by using the Windows host IP visible from WSL2: +# List all tags +ansible-playbook playbooks/provision.yml --list-tags -- `172.19.144.1:2222` +# Run only docker role tasks +ansible-playbook playbooks/provision.yml --tags docker -### 2. Ansible configuration ignored in writable directory -When the project was executed from a Windows-mounted path, Ansible ignored the local `ansible.cfg`. -This was resolved by copying the project to the native WSL filesystem and explicitly exporting: +# Run only docker installation tasks +ansible-playbook playbooks/provision.yml --tags docker_install -`ANSIBLE_CONFIG=/home/dorley/work/ansible/ansible.cfg` +# Install packages only +ansible-playbook playbooks/provision.yml --tags packages -### 3. Missing deployment variable -Initial deployment failed because `dockerhub_username` was undefined. -This was resolved by defining the variable in inventory group variables. +# Skip common role +ansible-playbook playbooks/provision.yml --skip-tags common +``` -### 4. Transient Docker Hub network timeout -One deployment attempt failed with a TLS handshake timeout when pulling the image from Docker Hub. -Manual pull confirmed that the image reference was correct and the issue was transient. +--- -### 5. Final Docker Compose YAML error -The remaining unresolved issue is a YAML syntax problem in the generated Compose file. -This is the last known blocker preventing the application from starting. +## Docker Compose Migration -## Conclusion +### Role rename -The lab is largely completed and validated: +The deployment role was renamed from `app_deploy` to `web_app`. +The old `roles/app_deploy/` directory was removed from the final submission. -- provisioning works -- role refactoring is implemented -- static checks pass -- deployment logic is mostly correct -- the local free execution model on Windows + WSL2 + Vagrant is valid +### Compose template -The only remaining issue is the final Docker Compose YAML rendering error in the generated application manifest. Once the template is corrected, the deployment should complete successfully. +File: `roles/web_app/templates/docker-compose.yml.j2` +Template supports: + +- `app_name` +- `docker_image` +- `docker_tag` +- `app_port` / `app_internal_port` +- `app_env` (dict of extra env vars) +- optional `app_secret_key` (Vault-friendly) + +It also sets sane defaults for the provided Flask app: + +- `HOST=0.0.0.0` +- `PORT={{ app_internal_port }}` + +### Role dependency + +File: `roles/web_app/meta/main.yml` + +`web_app` depends on `docker`, so a playbook that runs the whole `web_app` role will install Docker first. + +Important practical note: + +- `ansible-playbook playbooks/deploy.yml` is the safest default. +- `ansible-playbook playbooks/deploy.yml --tags web_app` also works for selective role execution. +- `ansible-playbook playbooks/deploy.yml --tags app_deploy` targets only the deployment block and is **not** the best choice if you expect role dependencies to be selected by tag filtering. + +### Deployment implementation + +File: `roles/web_app/tasks/main.yml` + +Deployment flow: + +1. Create `/opt/{{ app_name }}`. +2. Render `docker-compose.yml`. +3. Optional `docker login` (only if password is provided). +4. Pull the image using `community.docker.docker_image`. +5. Run `community.docker.docker_compose_v2` (`state: present`). +6. Verify app responds on `/health` and `/`. + +Idempotency test: + +```bash +ansible-playbook playbooks/deploy.yml +ansible-playbook playbooks/deploy.yml +``` + +The second run should show mostly `ok` tasks, and no container recreation unless inputs changed. + +--- + +## Wipe Logic + +Files: + +- `roles/web_app/defaults/main.yml` +- `roles/web_app/tasks/wipe.yml` +- `roles/web_app/tasks/main.yml` (includes wipe first) + +### Why variable + tag? + +This is a **double safety mechanism**: + +- Variable (`web_app_wipe=true`) prevents accidental wipe during normal runs. +- Tag (`--tags web_app_wipe`) enables a wipe-only run that does not deploy. + +### Test scenarios + +```bash +# 1) Normal deployment (wipe does NOT run) +ansible-playbook playbooks/deploy.yml + +# 2) Wipe only +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe + +# 3) Clean reinstall (wipe -> deploy) +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" + +# 4a) Safety check: tag without variable => wipe skipped +ansible-playbook playbooks/deploy.yml --tags web_app_wipe +``` + +--- + +## CI/CD Integration + +File: `.github/workflows/ansible-deploy.yml` + +### Workflow architecture + +Two-stage pipeline: + +1. **lint** (GitHub-hosted runner) + - installs Ansible + ansible-lint + - installs collections from `ansible/requirements.yml` + - runs `ansible-lint -p .` inside the `ansible/` directory. + +2. **deploy** (self-hosted Linux runner) + - runs on `runs-on: [self-hosted, linux]` + - installs Ansible on the runner + - installs collections from `ansible/requirements.yml` + - runs `ansible-playbook playbooks/deploy.yml` + - verifies the app with `curl /health` and `curl /`. + +### Why a Linux self-hosted runner? + +For a local Vagrant/VirtualBox environment behind NAT, a **Linux self-hosted runner** on the same machine is the simplest free option. +Using WSL2 Ubuntu is practical because: + +- the workflow already uses Bash and Linux paths +- Ansible is easier to maintain on Linux +- no public inbound SSH exposure is required +- it matches the local, free, Windows-friendly lab setup + +### Secrets + +Recommended secrets (Actions → Secrets): + +- `ANSIBLE_VAULT_PASSWORD` — only needed if you use Vault-encrypted files +- `VM_HOST` — host/IP used by the verification curl step (default fallback: `127.0.0.1`) + +If you later move to a public VM and a GitHub-hosted deploy job, add: + +- `SSH_PRIVATE_KEY` +- `VM_USER` + +--- + +## Testing Results Checklist + +Collect evidence for submission: + +- `ansible-playbook playbooks/provision.yml --list-tags` +- `ansible-playbook playbooks/provision.yml --tags docker_install` +- `ansible-playbook playbooks/deploy.yml` +- second deployment run for idempotency proof +- wipe-only run: + - `ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe` +- clean reinstall run: + - `ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true"` +- rendered file: + - `/opt/{{ app_name }}/docker-compose.yml` +- app responses: + - `curl http://:{{ app_port }}/health` + - `curl http://:{{ app_port }}/` +- GitHub Actions logs for `lint` and `deploy` + +--- + +## Challenges & Solutions + +- **Compose v2 on Ubuntu**: the docker role installs `docker-compose-plugin` so `docker compose` is available. +- **Idempotency vs pull policy**: image pulling is done explicitly via `docker_image`, while Compose uses `pull: never`. +- **Safety for destructive operations**: wipe logic requires both a boolean variable and, for wipe-only mode, an explicit tag. +- **Collection portability**: `ansible.posix` was added explicitly so `authorized_key` does not depend on the full `ansible` meta-package. + +--- + +## Research Answers + +### Blocks & Tags + +1. **What happens if the rescue block also fails?** + - The whole block is considered failed and Ansible stops the play (unless errors are ignored). + - The `always` section still runs. + +2. **Can you have nested blocks?** + - Yes. This lab uses an outer block with an `always` section, and inner blocks for `packages` / `users`. + +3. **How do tags inherit to tasks within blocks?** + - Tags defined at a block level are applied to all tasks in that block. + - Tags defined at role level (in playbooks) also apply to all tasks in the role. + +### Docker Compose + +1. **Difference between `restart: always` and `restart: unless-stopped`?** + - `always` restarts containers even if you manually stopped them. + - `unless-stopped` restarts containers automatically except when they were explicitly stopped by an operator. + +2. **How do Compose networks differ from Docker bridge networks?** + - Compose creates and manages networks as part of an application project, with predictable naming. + - A classic Docker bridge network is a lower-level primitive not tied to a Compose project. + +3. **Can you reference Ansible Vault variables in the template?** + - Yes. Vault-encrypted variables are decrypted at runtime and can be used like normal variables in templates. + +### Wipe logic + +1. **Why use both variable AND tag?** + - The variable protects normal runs. + - The tag enables a wipe-only execution that skips deployment tasks. + +2. **Difference between `never` tag and this approach?** + - `never` requires explicit `--tags never` (or another selected tag on the same task) and can be confusing. + - Variable + tag is explicit and easier to reason about for safety-critical operations. + +3. **Why must wipe logic come BEFORE deployment?** + - It enables the clean reinstall flow: remove old state first, then deploy from scratch. + +4. **When do you want clean reinstall vs rolling update?** + - Clean reinstall: broken state, changed config/layout, major upgrades, testing fresh installs. + - Rolling update: keep service available, minimize downtime, gradual rollout. + +5. **How to extend wipe to remove images/volumes too?** + - Add Compose options such as `remove_volumes: true` when needed. + - Remove images via `community.docker.docker_image state: absent`. + - Remove named volumes explicitly with Docker modules or CLI commands. + +### CI/CD + +1. **Security implications of storing SSH keys in GitHub Secrets?** + - Secrets can be exfiltrated if workflows are compromised. + - Limit key scope, use dedicated deploy keys, restrict repo access, and rotate regularly. + +2. **How to implement staging → production pipeline?** + - Use separate environments, separate inventories, and manual approvals for production. + - Another option is separate workflows triggered on tags/releases. + +3. **What to add for rollbacks?** + - Pin image tags, keep the previous known-good tag, and add a rollback job that redeploys it. + +4. **How does a self-hosted runner improve security compared to GitHub-hosted?** + - SSH keys/secrets do not need to be sent to GitHub-hosted machines. + - The runner stays inside your controlled network perimeter. diff --git a/ansible/docs/LOCAL_VALIDATION_WINDOWS.md b/ansible/docs/LOCAL_VALIDATION_WINDOWS.md new file mode 100644 index 0000000000..7d7ba30340 --- /dev/null +++ b/ansible/docs/LOCAL_VALIDATION_WINDOWS.md @@ -0,0 +1,282 @@ +# Local Validation Guide (Windows + WSL2 + Vagrant + VirtualBox) + +This guide is designed for a **fully local and free** Lab06 workflow on **Windows**. +It assumes: + +- Windows 10/11 +- VirtualBox +- Vagrant +- WSL2 with Ubuntu +- GitHub repository (optional, only for CI/CD) + +--- + +## 1. Recommended Setup + +### Windows side + +Install and verify: + +```powershell +vagrant --version +VBoxManage --version +git --version +``` + +Recommended Vagrant plugins check: + +```powershell +vagrant plugin list +``` + +### WSL2 Ubuntu side + +Install and verify: + +```bash +wsl --status +python3 --version +pip3 --version +ssh -V +git --version +``` + +Install Ansible in WSL2 if missing: + +```bash +python3 -m pip install --user --upgrade pip +python3 -m pip install --user ansible ansible-lint +printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> ~/.bashrc +source ~/.bashrc +ansible --version +ansible-lint --version +``` + +--- + +## 2. Start the VM + +Run in **PowerShell** from the repository root: + +```powershell +vagrant up +vagrant status +vagrant port +``` + +Expected important forwarded ports: + +- guest SSH `22` -> host `2222` +- guest app `8000` -> host `8000` + +If ports changed because of `auto_correct`, update the inventory accordingly. + +--- + +## 3. Prepare the SSH key for Ansible + +The Vagrant private key is usually stored under the project directory on Windows. +Copy it into WSL and fix permissions. + +Example from WSL: + +```bash +mkdir -p ~/.ssh +cp /mnt/c/path/to/your/repo/.vagrant/machines/default/virtualbox/private_key ~/.ssh/lab05_vagrant_key +chmod 600 ~/.ssh/lab05_vagrant_key +``` + +Check SSH connectivity: + +```bash +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@127.0.0.1 +``` + +If `127.0.0.1` does not work from WSL2, detect the Windows host IP and try again: + +```bash +WIN_HOST_IP=$(grep -m1 nameserver /etc/resolv.conf | awk '{print $2}') +echo "$WIN_HOST_IP" +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@"$WIN_HOST_IP" +``` + +--- + +## 4. Inventory Settings + +Default local-friendly inventory example: + +```ini +[webservers] +vagrant1 ansible_host=127.0.0.1 ansible_port=2222 ansible_user=vagrant ansible_ssh_private_key_file=~/.ssh/lab05_vagrant_key + +[webservers:vars] +ansible_python_interpreter=/usr/bin/python3 +``` + +If WSL2 cannot reach `127.0.0.1:2222`, replace `ansible_host` with the detected Windows host IP. + +--- + +## 5. Install Ansible collections + +Run in WSL2 from the repository root: + +```bash +cd ansible +ansible-galaxy collection install -r requirements.yml +``` + +Verify installed collections: + +```bash +ansible-galaxy collection list | grep -E 'community.docker|community.general|ansible.posix' +``` + +--- + +## 6. Basic validation commands + +### Connectivity + +```bash +cd ansible +ansible all -m ping +``` + +### Syntax and task listing + +```bash +ansible-playbook playbooks/provision.yml --syntax-check +ansible-playbook playbooks/deploy.yml --syntax-check +ansible-playbook playbooks/provision.yml --list-tags +ansible-playbook playbooks/deploy.yml --list-tasks +ansible-playbook playbooks/deploy.yml --list-tasks --tags web_app +``` + +### Lint + +```bash +ansible-lint -p . +``` + +--- + +## 7. Provisioning validation + +```bash +cd ansible +ansible-playbook playbooks/provision.yml +ansible-playbook playbooks/provision.yml +``` + +The second run should be mostly idempotent. + +Selective tag checks: + +```bash +ansible-playbook playbooks/provision.yml --tags docker +ansible-playbook playbooks/provision.yml --tags docker_install +ansible-playbook playbooks/provision.yml --tags packages +ansible-playbook playbooks/provision.yml --skip-tags common +``` + +--- + +## 8. Deployment validation + +```bash +cd ansible +ansible-playbook playbooks/deploy.yml +ansible-playbook playbooks/deploy.yml +``` + +Verify on the target VM: + +```bash +ssh -i ~/.ssh/lab05_vagrant_key -p 2222 vagrant@127.0.0.1 +sudo docker ps +sudo docker compose -f /opt/devops-info-service/docker-compose.yml ps +curl http://127.0.0.1:8000/health +curl http://127.0.0.1:8000/ +``` + +From WSL2 / host side: + +```bash +curl http://127.0.0.1:8000/health +curl http://127.0.0.1:8000/ +``` + +If WSL2 cannot reach Windows localhost, replace `127.0.0.1` with the Windows host IP. + +--- + +## 9. Wipe logic validation + +### Wipe only + +```bash +cd ansible +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe +``` + +### Clean reinstall + +```bash +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" +``` + +### Safety check + +```bash +ansible-playbook playbooks/deploy.yml --tags web_app_wipe +``` + +Expected result: wipe tasks are selected by tag but skipped by the boolean guard. + +--- + +## 10. GitHub Actions self-hosted runner (free local option) + +Recommended approach: + +- Create the self-hosted runner in **WSL2 Ubuntu**, not in native Windows. +- Use labels that include `self-hosted` and `linux`. +- Run the runner only when you want to test CI locally. + +Why this is preferred: + +- the workflow uses Bash and Linux paths +- Ansible support is simpler on Linux +- no cloud VM is required +- no paid service is required + +Useful checks on the runner machine: + +```bash +python3 --version +ansible --version +ansible-galaxy collection list | grep -E 'community.docker|community.general|ansible.posix' +curl --version +``` + +Suggested repository secrets: + +- `ANSIBLE_VAULT_PASSWORD` (only if you really use Vault) +- `VM_HOST` with value `127.0.0.1` or the Windows host IP + +--- + +## 11. What to capture for submission + +Recommended evidence: + +1. `ansible-playbook playbooks/provision.yml --list-tags` +2. `ansible-playbook playbooks/provision.yml --tags docker_install` +3. First and second `ansible-playbook playbooks/deploy.yml` runs +4. Wipe-only run +5. Clean reinstall run +6. `docker compose ps` output on the VM +7. `curl /health` and `curl /` output +8. GitHub Actions `lint` and `deploy` job logs diff --git a/ansible/roles/web_app/templates/docker-compose.yml.j2 b/ansible/roles/web_app/templates/docker-compose.yml.j2 index 33a7107ab5..5ae69e2951 100644 --- a/ansible/roles/web_app/templates/docker-compose.yml.j2 +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -1,23 +1,22 @@ -version: '{{ docker_compose_version }}' +version: "{{ docker_compose_version }}" services: {{ app_name }}: - image: {{ docker_image }}:{{ docker_tag }} - container_name: {{ app_name }} + image: "{{ docker_image }}:{{ docker_tag }}" + container_name: "{{ app_name }}" ports: - "{{ app_port }}:{{ app_internal_port }}" environment: - # Defaults to make the provided Flask app listen on the expected port. HOST: "0.0.0.0" PORT: "{{ app_internal_port }}" -{%- if app_env is defined and app_env | length > 0 %} -{%- for key, value in app_env.items() %} +{% if app_env is defined and app_env | length > 0 %} +{% for key, value in app_env.items() %} {{ key }}: {{ value | quote }} -{%- endfor %} -{%- endif %} -{%- if app_secret_key is defined %} +{% endfor %} +{% endif %} +{% if app_secret_key is defined %} APP_SECRET_KEY: {{ app_secret_key | quote }} -{%- endif %} +{% endif %} restart: unless-stopped networks: - {{ app_name }}_net