---
- include_tasks: utils/once/flag.yml
# Exception: a first deploy has no container and a crash-looping one is no healthy engine; both mean nothing to carry, and the routine below still has to run to heal them.
- name: "๐ REDIS | Read whether an earlier deploy left a healthy engine"
ansible.builtin.shell: >
container inspect --type container --format '{{ "{{.State.Status}} {{.State.Health.Status}}" }}' {{ REDIS_CONTAINER_ADDRESS }}
register: redis_health_before
delegate_to: "{{ STACK_HOST }}"
run_once: true
changed_when: false
failed_when: false
- name: "๐ REDIS | Read the passwords the healthy engine accepts"
ansible.builtin.shell: >
{{ REDIS_EXEC_SHELL }}
'{{ REDIS_CLI }} ACL LIST'
register: redis_acl_before_restart
delegate_to: "{{ STACK_HOST }}"
run_once: true
changed_when: false
failed_when: redis_acl_before_restart.rc != 0 or redis_acl_before_restart.stdout_lines | select('match', '^user default ') | list | length != 1
when: redis_health_before.stdout | trim == 'running healthy'
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "๐ง Setup container network for {{ application_id }}"
include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/network/routine.yml') }}"
vars:
compose_handlers_flush: true
application_id: "svc-db-redis"
database_consumer_id: "svc-db-redis"
- name: "โณ REDIS | Wait until container is running (not restarting)"
ansible.builtin.shell: >
container inspect --type container --format '{{ "{{.State.Status}}" }} {{ "{{.State.Restarting}}" }}' {{ REDIS_CONTAINER_ADDRESS }}
register: redis_state
delegate_to: "{{ STACK_HOST }}"
run_once: true
retries: 120
delay: 2
until: >
(redis_state.stdout is search('running')) and
(redis_state.stdout is not search('true'))
changed_when: false
- name: "โ
REDIS | Confirm engine answers PING (authenticated)"
ansible.builtin.shell: >
{{ REDIS_EXEC_SHELL }}
'{{ REDIS_CLI }} ping'
register: redis_ping_ready
delegate_to: "{{ STACK_HOST }}"
run_once: true
retries: 30
delay: 2
until: redis_ping_ready.stdout is search('PONG')
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "๐งฎ REDIS | Pick the passwords a rotated REDIS_PASSWORD replaced"
ansible.builtin.set_fact:
redis_retired_password_hashes: >-
{{
(redis_acl_before_restart.stdout_lines if redis_acl_before_restart.stdout_lines is defined else [])
| select('match', '^user default ')
| join(' ')
| regex_findall('(?:^|\s)#([0-9a-f]{64})(?=\s|$)')
| difference([REDIS_PASSWORD | hash('sha256')])
| sort
}}
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# Exception: the restart dropped every password but the new one, while the shared consumers deployed later in this pass still hold the old one. tasks/utils/rotation/retire.yml drops them once every server role has redeployed.
- name: "๐ REDIS | Keep accepting the replaced passwords until the roles redeployed"
ansible.builtin.shell: >
{{ REDIS_EXEC_SHELL }}
'{{ REDIS_CLI }} ACL SETUSER default{% for retired_hash in redis_retired_password_hashes %} "#{{ retired_hash }}"{% endfor %}'
register: redis_acl_carried
delegate_to: "{{ STACK_HOST }}"
run_once: true
changed_when: true
failed_when: redis_acl_carried.rc != 0 or redis_acl_carried.stdout | trim != 'OK'
when: redis_retired_password_hashes | length > 0
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"