From e4e7d94694f31d8416b286af34ac3feaf7536404 Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Mon, 31 Mar 2025 20:45:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=BE=D0=BB=D1=8C=20=D1=81=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BB=20=D0=B1=D0=B5=D0=B7=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ansible-lint | 4 +- .gitlab-ci.yml | 31 ++++--- .idea/encodings.xml | 6 ++ Dockerfile | 2 + Dockerfile-CentOS | 4 +- Dockerfile-Ubuntu | 4 +- Makefile | 6 +- README.md | 63 +++++++++++++- ansible.cfg | 5 +- history/.gitkeep | 0 molecule/default/converge.yml | 3 +- molecule/default/molecule.yml | 23 +++-- patroni_config.yaml | 5 ++ roles/apply/defaults/.gitkeep | 0 roles/apply/deploy.yaml | 11 +++ roles/apply/files/.gitkeep | 0 roles/apply/handlers/.gitkeep | 0 roles/apply/handlers/main.yaml | 4 + roles/apply/meta/.gitkeep | 0 roles/apply/tasks/main.yaml | 3 + roles/apply/tasks/role/main.yaml | 130 +++++++++++++++++++++++++++++ roles/apply/templates/.gitkeep | 0 roles/apply/tests/.gitkeep | 0 roles/deploy.yaml | 6 +- roles/prepare/defaults/.gitkeep | 0 roles/prepare/deploy.yaml | 11 +++ roles/prepare/files/.gitkeep | 0 roles/prepare/handlers/.gitkeep | 0 roles/prepare/meta/.gitkeep | 0 roles/prepare/tasks/main.yaml | 15 ++++ roles/prepare/tasks/role/main.yaml | 128 ++++++++++++++++++++++++++++ roles/prepare/templates/.gitkeep | 0 roles/prepare/tests/.gitkeep | 0 vars/secrets.yml | 10 +++ vault-password.txt | 2 +- 35 files changed, 432 insertions(+), 44 deletions(-) create mode 100644 .idea/encodings.xml create mode 100644 history/.gitkeep create mode 100644 patroni_config.yaml create mode 100644 roles/apply/defaults/.gitkeep create mode 100644 roles/apply/deploy.yaml create mode 100644 roles/apply/files/.gitkeep create mode 100644 roles/apply/handlers/.gitkeep create mode 100644 roles/apply/handlers/main.yaml create mode 100644 roles/apply/meta/.gitkeep create mode 100644 roles/apply/tasks/main.yaml create mode 100644 roles/apply/tasks/role/main.yaml create mode 100644 roles/apply/templates/.gitkeep create mode 100644 roles/apply/tests/.gitkeep create mode 100644 roles/prepare/defaults/.gitkeep create mode 100644 roles/prepare/deploy.yaml create mode 100644 roles/prepare/files/.gitkeep create mode 100644 roles/prepare/handlers/.gitkeep create mode 100644 roles/prepare/meta/.gitkeep create mode 100644 roles/prepare/tasks/main.yaml create mode 100644 roles/prepare/tasks/role/main.yaml create mode 100644 roles/prepare/templates/.gitkeep create mode 100644 roles/prepare/tests/.gitkeep create mode 100644 vars/secrets.yml diff --git a/.ansible-lint b/.ansible-lint index 1646c4f..a14585e 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -2,6 +2,6 @@ skip_list: - fqcn - yaml[new-line-at-end-of-file] - yaml[truthy] - - yaml[line-length] + - yaml[line-length] - var-naming[no-role-prefix] - - 'ignore-errors' \ No newline at end of file + - 'ignore-errors' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 791c0d1..5d1e454 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ stages: - lint - - test +# - test - deploy - notify @@ -33,18 +33,18 @@ lint: rules: - if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master" -test: - stage: test - script: - - echo "Распаковываем секреты..." - - ansible-vault decrypt --vault-password-file ./vault-password.txt vars/secrets.yml - - echo "Запускаем тесты через Молекулу..." - - molecule test --parallel --destroy=always - - echo "Упаковываем секреты..." - - ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file ./vault-password.txt - allow_failure: false - rules: - - if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master" +#test: +# stage: test +# script: +# - echo "Распаковываем секреты..." +# - ansible-vault decrypt --vault-password-file ./vault-password.txt vars/secrets.yml +# - echo "Запускаем тесты через Молекулу..." +# - molecule test --parallel --destroy=always +# - echo "Упаковываем секреты..." +# - ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file ./vault-password.txt +# allow_failure: false +# rules: +# - if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master" deploy: stage: deploy @@ -74,9 +74,9 @@ notify: script: - | if [ "$CI_JOB_STATUS" == "success" ]; then - MESSAGE="✅ Пайплайн успешно завершен!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS" + MESSAGE="✅ Настройки кластера успешно завершены!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS" else - MESSAGE="❌ Пайплайн завершен с ошибкой!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS" + MESSAGE="❌ Настройки кластера были произведены с ошибкой!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS" fi # curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \ # -d "chat_id=$TELEGRAM_CHAT_ID" \ @@ -86,4 +86,3 @@ notify: after_script: - echo "Работа пайплайна завершена" - diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..38aa11a --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index aa8fc36..5bef48c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,8 @@ RUN apt-get update && \ ca-certificates \ curl \ gnupg \ + diffutils \ + jq \ lsb-release \ && mkdir -p /etc/apt/keyrings \ && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ diff --git a/Dockerfile-CentOS b/Dockerfile-CentOS index 0e4a063..e7fc6b6 100644 --- a/Dockerfile-CentOS +++ b/Dockerfile-CentOS @@ -7,7 +7,7 @@ USER root # Обновляем пакеты и устанавливаем systemd RUN dnf -y update && \ - dnf -y install systemd rsync && \ + dnf -y install systemd rsync diffutils jq && \ dnf clean all && \ rm -rf /var/cache/dnf /tmp/* /var/tmp/* @@ -19,4 +19,4 @@ STOPSIGNAL SIGRTMIN+3 VOLUME [ "/sys/fs/cgroup" ] # Запускаем systemd при старте контейнера -CMD ["/sbin/init"] \ No newline at end of file +CMD ["/sbin/init"] diff --git a/Dockerfile-Ubuntu b/Dockerfile-Ubuntu index 7c3ed80..083004a 100644 --- a/Dockerfile-Ubuntu +++ b/Dockerfile-Ubuntu @@ -5,7 +5,7 @@ FROM geerlingguy/docker-ubuntu2004-ansible:latest # Обновляем пакеты и устанавливаем systemd RUN apt-get update && \ - apt-get install -y systemd systemd-sysv rsync && \ + apt-get install -y systemd systemd-sysv rsync diffutils jq && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* @@ -17,4 +17,4 @@ STOPSIGNAL SIGRTMIN+3 VOLUME [ "/sys/fs/cgroup" ] # Запускаем systemd при старте контейнера -CMD ["/sbin/init"] \ No newline at end of file +CMD ["/sbin/init"] diff --git a/Makefile b/Makefile index 7d96b56..c3ed1dc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Глобальные переменные IMAGE ?= ansible TAG ?= 0.1 -REGISTRY ?= hub.cism-ms.ru/ansible +REGISTRY ?= hub.antropoff.ru/ansible # По умолчанию используем docker. Для локальной разработки используйте docker-compose RUN_MODE ?= docker @@ -117,7 +117,7 @@ role: clear; \ echo "Running test roles..."; \ $(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml"; \ - $(RUN) bash -c "docker login $(REGISTRY) && molecule test --parallel --destroy=always"; \ + $(RUN) bash -c "molecule test --parallel --destroy=always"; \ $(RUN) bash -c "ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file vault-password.txt";; \ deploy) \ clear; \ @@ -148,4 +148,4 @@ git: git checkout -b $$NEW_BRANCH; \ echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \ *) echo "Unknown action. Available actions: push, pull, cluster-branch";; \ - esac \ No newline at end of file + esac diff --git a/README.md b/README.md index 4c967db..360c880 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,63 @@ -# AnsibleTemplate +# Patroni GitOps Config Manager -Темплейт для создания, проверки и тестирование ролей Ansible с помощью контейнеров Docker. +### Назначение роли +Эта роль предназначена для безопасного управления конфигурацией кластера Patroni. Она предоставляет следующие возможности: + +- Создание резервных копий текущей конфигурации с timestamp +- Валидация изменений конфигурации перед применением +- Наглядное отображение различий между текущей и новой конфигурацией +- Безопасное применение изменений через REST API Patroni +- Проверка состояния кластера после изменений +- Уведомление о необходимости перезагрузки нод (если требуется) +- Автоматическое управление историей конфигурационных файлов + +### Требования +- Ansible 2.9+ +- Доступ к Patroni REST API (порт 8008) +- Python 3 для валидации YAML +- Утилита diff с поддержкой цветного вывода + +### Переменные роли +Основные переменные, которые можно переопределить: + +- config_dir (по умолчанию: "/ansible/history") - директория для хранения истории конфигураций +- config_file (по умолчанию: "/ansible/patroni_config.yaml") - путь к файлу с изменениями конфигурации +- patroni_host (по умолчанию: "10.14.0.180") - хост кластера Patroni + +### Анализ результатов +После выполнения этапа подготовки: + +- Будет показан цветной diff изменений +- В директории config_dir появятся: +- Резервная копия текущей конфигурации с timestamp +- Файл last.yaml с новой конфигурацией + +После выполнения этапа применения: + +- Будет выведен статус кластера +- Появится предупреждение, если требуется перезагрузка нод +- Старые конфигурации будут автоматически удалены (остаются последние 10) + +### Особенности работы +Безопасность: +- Конфигурация всегда сохраняется перед изменениями +- Изменения проверяются на валидность перед применением +- Показывается подробный diff перед применением + +Информирование: +- Явное предупреждение о необходимости перезагрузки +- Подробный вывод статуса кластера после изменений +- Сохранение истории изменений + +Автоматизация: +- Управление историей конфигураций (автоочистка старых файлов) +- Проверка состояния кластера после изменений + +### Важные примечания +- Роль не выполняет автоматическую перезагрузку нод Patroni, так как это потенциально опасная операция +- При необходимости перезагрузки будет показана команда для ручного выполнения +- Все изменения конфигурации сохраняются с timestamp для возможности отката +- Для работы требуется доступ к API Patroni (порт 8008) ### С чего начать? @@ -58,4 +115,4 @@ molecule/default/molecule.yml volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro ``` -Помните, что образ обязательно должен содержать python не ниже версии 3.12 и systemd для нормального тестирования ролей. \ No newline at end of file +Помните, что образ обязательно должен содержать python не ниже версии 3.12 и systemd для нормального тестирования ролей. diff --git a/ansible.cfg b/ansible.cfg index d43f8b2..bf2dee5 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -5,4 +5,7 @@ remote_user = devops host_key_checking = False enable_plugins = yaml, ini roles_path = roles/ -interpreter_python = auto \ No newline at end of file +interpreter_python = auto +stdout_callback = yaml +bin_ansible_callbacks = True +force_color = 1 diff --git a/history/.gitkeep b/history/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index 749dcda..462c2a4 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -3,4 +3,5 @@ hosts: all vars_files: - ../../vars/secrets.yml - roles: \ No newline at end of file + roles: + - ../../roles/prepare diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index 9ceea66..086fee3 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -9,18 +9,18 @@ driver: name: docker platforms: - - name: centos - image: "hub.cism-ms.ru/ansible/centos:latest" - privileged: true - pre_build_image: true - volumes: - - /sys/fs/cgroup:/sys/fs/cgroup:ro - - /var/run/docker.sock:/var/run/docker.sock - tmpfs: - - /tmp - - /run +# - name: centos +# image: "hub.antropoff.ru/ansible/centos:latest" +# privileged: true +# pre_build_image: true +# volumes: +# - /sys/fs/cgroup:/sys/fs/cgroup:ro +# - /var/run/docker.sock:/var/run/docker.sock +# tmpfs: +# - /tmp +# - /run - name: ubuntu - image: "hub.cism-ms.ru/ansible/ubuntu:latest" + image: "hub.antropoff.ru/ansible/ubuntu:latest" privileged: true pre_build_image: true volumes: @@ -58,4 +58,3 @@ scenario: - verify - cleanup - destroy - diff --git a/patroni_config.yaml b/patroni_config.yaml new file mode 100644 index 0000000..1f3d7e4 --- /dev/null +++ b/patroni_config.yaml @@ -0,0 +1,5 @@ +postgresql: + parameters: + max_connections: 300 + shared_buffers: "12GB" + use_pg_rewind: true diff --git a/roles/apply/defaults/.gitkeep b/roles/apply/defaults/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/apply/deploy.yaml b/roles/apply/deploy.yaml new file mode 100644 index 0000000..24e6197 --- /dev/null +++ b/roles/apply/deploy.yaml @@ -0,0 +1,11 @@ +--- +- name: Deploy roles + hosts: localhost + become: true + become_user: root + become_method: ansible.builtin.sudo + gather_facts: true + vars_files: + - ../../vars/secrets.yml + roles: + - apply \ No newline at end of file diff --git a/roles/apply/files/.gitkeep b/roles/apply/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/apply/handlers/.gitkeep b/roles/apply/handlers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/apply/handlers/main.yaml b/roles/apply/handlers/main.yaml new file mode 100644 index 0000000..127cc2f --- /dev/null +++ b/roles/apply/handlers/main.yaml @@ -0,0 +1,4 @@ +--- +- name: Log cleanup results + ansible.builtin.debug: + msg: "Removed {{ (old_configs.files | sort(attribute='mtime'))[:-10] | length }} old config files" diff --git a/roles/apply/meta/.gitkeep b/roles/apply/meta/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/apply/tasks/main.yaml b/roles/apply/tasks/main.yaml new file mode 100644 index 0000000..7ea19d9 --- /dev/null +++ b/roles/apply/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- name: "Подключаем таски" + include_tasks: "role/main.yaml" \ No newline at end of file diff --git a/roles/apply/tasks/role/main.yaml b/roles/apply/tasks/role/main.yaml new file mode 100644 index 0000000..8bbcb96 --- /dev/null +++ b/roles/apply/tasks/role/main.yaml @@ -0,0 +1,130 @@ +--- +- name: Apply new configuration + ansible.builtin.uri: + url: "http://{{ patroni_host }}:8008/config" + method: PATCH + body: "{{ new_config | to_json }}" + body_format: json + status_code: 200 + headers: + Content-Type: "application/json" + register: apply_result + changed_when: apply_result.status == 200 + +- name: Force wait for config to apply # noqa: no-handler + ansible.builtin.wait_for: + timeout: 30 + delay: 5 + when: apply_result is changed + +- name: Get verified cluster status # noqa: no-handler + ansible.builtin.uri: + url: "http://{{ patroni_host }}:8008/cluster" + method: GET + return_content: yes + status_code: 200 + register: verified_cluster_status + delegate_to: localhost + connection: local + when: apply_result is changed + +- name: Display confirmed cluster status + ansible.builtin.debug: + msg: | + === CONFIRMED CLUSTER STATUS === + Leader: {{ (verified_cluster_status.json.members | selectattr('role', 'equalto', 'leader') | map(attribute='name') | first) | default('UNKNOWN') }} + Members: + {% for member in verified_cluster_status.json.members %} + - {{ member.name }} [{{ member.role | upper }}] + State: {{ member.state | default('UNKNOWN') }} + Lag: {{ member.lag | default(0) }}MB + Timeline: {{ member.timeline | default('N/A') }} + Pending restart: {{ member.pending_restart | default(false) | ternary('YES', 'NO') }} + {% endfor %} + Config Applied: {{ apply_result is changed | ternary('YES', 'NO') }} + ================================ + delegate_to: localhost + connection: local + run_once: true + +- name: Refresh cluster status + ansible.builtin.uri: + url: "http://{{ patroni_host }}:8008/cluster" + method: GET + return_content: yes + status_code: 200 + register: refreshed_cluster_status + delegate_to: localhost + run_once: true + when: verified_cluster_status is defined + +- name: Safe check for pending restarts + ansible.builtin.set_fact: + needs_restart: >- + {{ + (refreshed_cluster_status.json.members | + map(attribute='pending_restart', default=false) | + select('equalto', true) | list | count > 0) or + (refreshed_cluster_status.json.members | + map(attribute='tags.pending_restart', default=false) | + select('equalto', true) | list | count > 0) + }} + node_names: >- + {{ + refreshed_cluster_status.json.members | + map(attribute='name') | + list + }} + when: + - refreshed_cluster_status.json is defined + - refreshed_cluster_status.json.members is defined + run_once: true + delegate_to: localhost + +- name: Show restart warning if needed + ansible.builtin.debug: + msg: | + {% if needs_restart %} + ================================== + ВНИМАНИЕ: ТРЕБУЕТСЯ ПЕРЕЗАГРУЗКА + ================================== + + Не, я конечно могу и сам ролью, но вдруг кластер в проде или еще где!!! + Так что лучше выполнить следующую команду на одной из нод кластера: + + patronictl restart {{ node_names | join(' ') }} + + Затронутые ноды: + {% for node in node_names %} + - {{ node }} + {% endfor %} + {% else %} + ================================== + СТАТУС: Перезагрузка не требуется + ================================== + {% endif %} + delegate_to: localhost + run_once: true + +- name: Archive old configurations + block: + - name: Find old config files + ansible.builtin.find: + path: "{{ config_dir }}" + pattern: "*-config.yaml" + age: "10s" + register: old_configs + delegate_to: localhost + connection: local + + - name: Remove excess configs (keep last 10) + ansible.builtin.file: + path: "{{ item.path }}" + state: absent + loop: "{{ (old_configs.files | sort(attribute='mtime'))[:-10] }}" + when: + - old_configs.matched > 10 + - apply_result is changed + delegate_to: localhost + connection: local + notify: Log cleanup results diff --git a/roles/apply/templates/.gitkeep b/roles/apply/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/apply/tests/.gitkeep b/roles/apply/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/deploy.yaml b/roles/deploy.yaml index 73b314f..a1f20d5 100644 --- a/roles/deploy.yaml +++ b/roles/deploy.yaml @@ -1 +1,5 @@ ---- \ No newline at end of file +--- +- name: Подготовка ро�к изменению конфнастроек кла�стера + import_playbook: prepare/deploy.yaml +- name: Применение изменений нас�троек кластера + import_playbook: apply/deploy.yaml diff --git a/roles/prepare/defaults/.gitkeep b/roles/prepare/defaults/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/prepare/deploy.yaml b/roles/prepare/deploy.yaml new file mode 100644 index 0000000..1cb5538 --- /dev/null +++ b/roles/prepare/deploy.yaml @@ -0,0 +1,11 @@ +--- +- name: Deploy roles + hosts: localhost + become: true + become_user: root + become_method: ansible.builtin.sudo + gather_facts: true + vars_files: + - ../../vars/secrets.yml + roles: + - prepare \ No newline at end of file diff --git a/roles/prepare/files/.gitkeep b/roles/prepare/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/prepare/handlers/.gitkeep b/roles/prepare/handlers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/prepare/meta/.gitkeep b/roles/prepare/meta/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/prepare/tasks/main.yaml b/roles/prepare/tasks/main.yaml new file mode 100644 index 0000000..86dc755 --- /dev/null +++ b/roles/prepare/tasks/main.yaml @@ -0,0 +1,15 @@ +--- +# - name: "Определяем ОС" +# set_fact: +# os_family: "{{ ansible_facts['os_family'] }}" + +# - name: "Подключаем таски для RedHat совместимых" +# include_tasks: "redhat/main.yaml" +# when: os_family == "RedHat" + +# - name: "Подключаем таски для Debian/Ubuntu совместимых" +# include_tasks: "debian/main.yaml" +# when: os_family == "Debian" + +- name: "Подключаем таски" + include_tasks: "role/main.yaml" \ No newline at end of file diff --git a/roles/prepare/tasks/role/main.yaml b/roles/prepare/tasks/role/main.yaml new file mode 100644 index 0000000..13e539b --- /dev/null +++ b/roles/prepare/tasks/role/main.yaml @@ -0,0 +1,128 @@ +--- +- name: Validate config file exists + ansible.builtin.stat: + path: "{{ config_file }}" + register: config_check + delegate_to: localhost + connection: local + +- name: Fail if config missing + ansible.builtin.fail: + msg: "Configuration file {{ config_file }} not found!" + when: not config_check.stat.exists + +- name: Ensure history directory exists + ansible.builtin.file: + path: "{{ config_dir }}" + state: directory + mode: '0750' + owner: "{{ ansible_user_id | default(omit) }}" + group: "{{ ansible_user_gid | default(omit) }}" + delegate_to: localhost + connection: local + changed_when: false + +- name: Get current Patroni configuration + ansible.builtin.uri: + url: "http://{{ patroni_host }}:8008/config" + method: GET + return_content: yes + status_code: 200 + register: patroni_config + changed_when: false + +- name: Save current config with timestamp + ansible.builtin.copy: + content: | + # Original config from {{ ansible_date_time.iso8601 }} + {{ patroni_config.content | from_json | to_nice_yaml }} + dest: "{{ config_dir }}/{{ ansible_date_time.iso8601 | replace('T', '_') | replace(':', '-') | replace('+', '-UTC') }}-config.yaml" + mode: '0640' + owner: "{{ ansible_user_id | default(omit) }}" + group: "{{ ansible_user_gid | default(omit) }}" + delegate_to: localhost + connection: local + changed_when: false + +- name: Load configuration changes + ansible.builtin.include_vars: + file: "{{ config_file }}" + name: config_changes + delegate_to: localhost + connection: local + +- name: Create merged configuration + ansible.builtin.set_fact: + new_config: "{{ patroni_config.content | from_json | combine(config_changes, recursive=True) }}" + +- name: Validate configuration changes + ansible.builtin.command: + cmd: | + python3 -c ' + import yaml, json, sys; + try: + yaml.safe_load(sys.stdin.read()) + sys.exit(0) + except Exception as e: + print(f"Invalid YAML: {str(e)}") + sys.exit(1)' + stdin: "{{ new_config | to_nice_yaml }}" + register: config_validation + changed_when: false + delegate_to: localhost + connection: local + +- name: Save new config as last.yaml + ansible.builtin.copy: + content: | + # Updated at {{ ansible_date_time.iso8601 }} + {{ new_config | to_nice_yaml }} + dest: "{{ config_dir }}/last.yaml" + mode: '0640' + owner: "{{ ansible_user_id | default(omit) }}" + group: "{{ ansible_user_gid | default(omit) }}" + delegate_to: localhost + connection: local + changed_when: false + +- name: Generate and display colored diff + block: + - name: Create temp files for diff + ansible.builtin.shell: | + cat > /tmp/old_config.yml << 'EOL' + {{ patroni_config.content | from_json | to_nice_yaml }} + EOL + cat > /tmp/new_config.yml << 'EOL' + {{ new_config | to_nice_yaml }} + EOL + delegate_to: localhost + connection: local + changed_when: false + + - name: Execute diff with colors + ansible.builtin.command: > + diff --color=always -u /tmp/old_config.yml /tmp/new_config.yml + register: diff_result + changed_when: diff_result.rc in [0,1] + failed_when: diff_result.rc > 1 + delegate_to: localhost + connection: local + + - name: Cleanup temp files + ansible.builtin.file: + path: "/tmp/{{ item }}" + state: absent + loop: + - old_config.yml + - new_config.yml + delegate_to: localhost + connection: local + changed_when: false + + - name: Display diff line by line + ansible.builtin.debug: + msg: "{{ item }}" + loop: "{{ diff_result.stdout_lines }}" + when: diff_result.stdout_lines | length > 0 + loop_control: + label: "" diff --git a/roles/prepare/templates/.gitkeep b/roles/prepare/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/roles/prepare/tests/.gitkeep b/roles/prepare/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/vars/secrets.yml b/vars/secrets.yml new file mode 100644 index 0000000..8bbfaa7 --- /dev/null +++ b/vars/secrets.yml @@ -0,0 +1,10 @@ +$ANSIBLE_VAULT;1.1;AES256 +37376136623761343135636239653137353661303631663536613265366431333339663866643265 +3033653765613632313661393166363238643137346330620a643233623433633963333035646466 +34633366623262643165326331333937623064356131306663623362663663343861383735616365 +3363646132393166310a353965346531616330396666383732656430633630323438326161323965 +64323865636265303331663166393232376138663965613361623361303663353737623238373435 +30316161616234356264643762653036626132613664316137646665323335663232393535353131 +37636331646364313839653438323461353638363936623131626161353936303839393533326162 +31623833313834646233303961656633633933386135396439373463623362316561313138643631 +6663 diff --git a/vault-password.txt b/vault-password.txt index 2d27916..ad366d9 100644 --- a/vault-password.txt +++ b/vault-password.txt @@ -1 +1 @@ -password123 \ No newline at end of file +password123