Files
computer-playbook/roles/web-app-stalwart/tasks/store_guard.yml
kevinveenbirkenbachandClaude Opus 5.5 4d9c3c1cfc refactor(web-app-stalwart): group the store SQL files and use the vars SPOT constant
- test_prefix_grouped_files: files/sql/store_database.sql and
  store_layout.sql become files/sql/store/database.sql and layout.sql.
- test_file_spot: test_store_guard.py builds the vars path with
  ROLE_FILE_VARS_MAIN instead of a hard-coded "vars" / "main.yml" chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 03:46:55 +01:00

124 lines
5.5 KiB
YAML

---
# Stalwart 1.x keeps a bytea column v in table u; every 0.x store keeps its bigint quota counters under the same name.
# mask_values scrubs the role's names out of psql errors, so existence comes from the shared catalog or the dedicated volume, and a stopped dedicated database fails its own assert instead of a masked psql error.
# The host marker outranks the store: a marked host that goes back to a 0.16 store (rollback, old backup) passes unless the marker is removed first.
- name: "🔍 Find the 1.x store marker of this host"
ansible.builtin.stat:
path: "{{ STALWART_STORE_MARKER_FILE }}"
register: stalwart_store_marker_file
- name: "📖 Read the 1.x store marker of this host"
ansible.builtin.slurp:
src: "{{ STALWART_STORE_MARKER_FILE }}"
register: stalwart_store_marker
when: stalwart_store_marker_file.stat.exists
- name: "🔧 Record whether this host last deployed Stalwart 1.x on its store"
ansible.builtin.set_fact:
stalwart_store_marked: >-
{{ stalwart_store_marker is not skipped
and (stalwart_store_marker.content | b64decode | trim) == (STALWART_STORE_LAYOUT_VERSION | string) }}
- name: "🔍 Find the store database on the shared PostgreSQL"
database_query:
config: >-
{{ lookup('database', application_id) | combine({
'username': 'postgres',
'name': 'postgres',
'password': lookup('config', 'svc-db-postgres', 'secrets.credentials.POSTGRES_PASSWORD'),
}) }}
query_file: "{{ role_path }}/files/sql/store/database.sql"
named_args:
name: "{{ lookup('database', application_id, 'name') }}"
expect_rows: true
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
register: stalwart_store_database
until: stalwart_store_database is succeeded
retries: 6
delay: 5
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
when:
- not (stalwart_store_marked | bool)
- STALWART_DATABASE_SHARED | bool
- name: "🔍 Find the volume of the dedicated store database"
ansible.builtin.command: >-
container volume ls -q --filter name=^{{ lookup('database', application_id, 'volume') }}$
register: stalwart_store_volume
changed_when: false
when:
- not (stalwart_store_marked | bool)
- not (STALWART_DATABASE_SHARED | bool)
- name: "🔧 Record whether a store exists that this host never deployed Stalwart 1.x on"
ansible.builtin.set_fact:
stalwart_store_exists: >-
{{ not (stalwart_store_marked | bool) and (
(stalwart_store_database.rows[0][0] | int > 0)
if STALWART_DATABASE_SHARED | bool
else (stalwart_store_volume.stdout | trim | length > 0)) }}
- name: "🔍 Find the running container of the dedicated store database"
ansible.builtin.command:
argv:
- container
- ps
- --quiet
- --filter
- status=running
- --filter
- >-
{{ ('label=com.docker.swarm.service.name=' ~ lookup('database', application_id, 'service_name'))
if DEPLOYMENT_MODE == 'swarm'
else ('name=^' ~ lookup('database', application_id, 'container') ~ '$') }}
register: stalwart_store_engine
changed_when: false
when:
- stalwart_store_exists | bool
- not (STALWART_DATABASE_SHARED | bool)
- name: "✅ Ensure the dedicated store database runs, so the deploy can read its layout"
ansible.builtin.assert:
that:
- stalwart_store_engine.stdout | trim | length > 0
fail_msg: >-
The dedicated store database of Stalwart, service '{{ lookup('database', application_id, 'host') }}'
of stack '{{ application_id | get_entity_name }}', is not running on {{ inventory_hostname }},
so the deploy cannot read the layout of the store in volume '{{ lookup('database', application_id, 'volume') }}'.
This host has no record of a Stalwart 1.x deploy ({{ STALWART_STORE_MARKER_FILE }}), so that store may predate 1.0,
and the deploy stops before Stalwart {{ lookup('config', application_id, 'services.stalwart.version') }} opens it.
Start that database from the stack this host last deployed, or finish the stalwart-migrate steps of
"Upgrading from 0.16" in roles/web-app-stalwart/README.md, then deploy again.
quiet: true
when: stalwart_store_engine is not skipped
- name: "🔍 Read the layout of the store Stalwart opens"
database_query:
config: "{{ lookup('database', application_id) }}"
query_file: "{{ role_path }}/files/sql/store/layout.sql"
expect_rows: true
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
register: stalwart_store_layout
until: stalwart_store_layout is succeeded
retries: 6
delay: 5
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
when: stalwart_store_exists | bool
- name: "✅ Ensure Stalwart opens no store from before 1.0"
ansible.builtin.assert:
that:
- stalwart_store_layout.rows[0][0] in ['bytea', 'absent']
fail_msg: >-
The store in database '{{ lookup('database', application_id, 'name') }}' predates Stalwart 1.0
(type of column u.v: {{ stalwart_store_layout.rows[0][0] }}, a 1.x store holds bytea).
Stalwart {{ lookup('config', application_id, 'services.stalwart.version') }} refuses to open it and has no in-place upgrade,
so the deploy stops before it starts Stalwart. Migrate the store into a new
database with stalwart-migrate and swap it in as '{{ lookup('database', application_id, 'name') }}',
following "Upgrading from 0.16" in roles/web-app-stalwart/README.md, then deploy again.
quiet: true
when: stalwart_store_layout is not skipped