Merge pull request 'feat(postgres) — PostGIS, posé par le playbook et non par une image custom' (#52) from arcodange/postgis-pour-kadans into main
This commit was merged in pull request #52.
This commit is contained in:
@@ -22,4 +22,45 @@ postgres:
|
||||
|
||||
pgbouncer:
|
||||
auth_user: &pgbouncer_auth pgbouncer_auth
|
||||
auth_user_password: *pgbouncer_auth
|
||||
auth_user_password: *pgbouncer_auth
|
||||
|
||||
# PostGIS — spatial types for the databases that ask for it.
|
||||
#
|
||||
# Kadans stores neighbourhood ("zone") outlines as real geometry, so it needs
|
||||
# geometry(MultiPolygon,4326), ST_Contains and a GiST index. The image stays
|
||||
# `postgres:16.3-alpine`: the extension is installed INTO the running container
|
||||
# by setup/postgres.yml, exactly like the pgbouncer role and the app databases
|
||||
# are created there. No custom image (founder's call, 2026-08-08).
|
||||
#
|
||||
# WARNING — this is a per-CONTAINER install, not a per-VOLUME one. `apk add`
|
||||
# writes to the container's writable layer, so recreating the container (image
|
||||
# change, `docker compose up --force-recreate`) REMOVES PostGIS while the data
|
||||
# keeps its geometry columns — every spatial query then fails until this
|
||||
# playbook runs again. The install task is therefore idempotent and runs after
|
||||
# every compose deploy, and it is the reason `postgis_verifier` exists below:
|
||||
# a silent absence would look like an application bug.
|
||||
postgis:
|
||||
# Only these databases get the extension. Adding one here is the whole change.
|
||||
databases:
|
||||
- kadans
|
||||
# The Alpine package. Pinned to a MAJOR line, not a patch: postgis 3.x
|
||||
# upgrades within a major are ABI-compatible with a given PostgreSQL major.
|
||||
paquet: postgis
|
||||
# ⚠ WHY THE COPY STEP EXISTS — measured on pi2 (arm64), 2026-08-08.
|
||||
# `apk add postgis` alone SUCCEEDS and `CREATE EXTENSION postgis` still fails:
|
||||
#
|
||||
# ERROR: extension "postgis" is not available
|
||||
# DETAIL: Could not open extension control file
|
||||
# "/usr/local/share/postgresql/extension/postgis.control"
|
||||
#
|
||||
# Alpine's package targets Alpine's own PostgreSQL layout
|
||||
# (/usr/share/postgresql16, /usr/lib/postgresql16), while the official
|
||||
# `postgres:16-alpine` image builds the server into /usr/local. The files are
|
||||
# on disk, the server looks elsewhere. Relocating them makes it work — proven
|
||||
# in a throwaway container: PostGIS 3.4 USE_GEOS=1 USE_PROJ=1, and a Lyon
|
||||
# polygon round-tripping through ST_GeomFromText/ST_AsGeoJSON.
|
||||
#
|
||||
# Do NOT "simplify" this to a bare `apk add`. An `apk add --simulate` reports
|
||||
# OK, the install reports OK, and the extension is still unusable.
|
||||
source_partagee: /usr/share/postgresql16/extension
|
||||
source_lib: /usr/lib/postgresql16
|
||||
@@ -25,6 +25,63 @@
|
||||
applications_databases:
|
||||
gitea: "{{ gitea_database }}"
|
||||
|
||||
# ── PostGIS ────────────────────────────────────────────────────────────
|
||||
# Installed INTO the running container rather than baked into a custom
|
||||
# image (founder's call, 2026-08-08). See group_vars/postgres/postgres.yml
|
||||
# for why the copy step is not optional, and for the durability caveat.
|
||||
#
|
||||
# Runs after the compose deploy above ON PURPOSE: if that task recreated
|
||||
# the container, the writable layer is fresh and PostGIS is gone with it.
|
||||
# This is what makes the pair (deploy, install) safe to replay.
|
||||
- name: Install PostGIS into the Postgres container
|
||||
ansible.builtin.shell: |
|
||||
set -eu
|
||||
docker exec {{ postgres_container_name }} sh -c '
|
||||
set -eu
|
||||
apk add --no-cache {{ postgis.paquet }} >/dev/null
|
||||
cp -r {{ postgis.source_partagee }}/* "$(pg_config --sharedir)/extension/"
|
||||
cp -r {{ postgis.source_lib }}/*.so "$(pg_config --pkglibdir)/"
|
||||
'
|
||||
# `apk add` is idempotent and the copies overwrite identical files, so a
|
||||
# replay changes nothing observable. We do not pretend to detect that:
|
||||
# claiming `changed_when: false` outright would hide a REAL first install.
|
||||
register: postgis_installation
|
||||
changed_when: "'Installing' in postgis_installation.stdout"
|
||||
|
||||
- name: Enable PostGIS on the databases that need it
|
||||
ansible.builtin.shell: |
|
||||
docker exec {{ postgres_container_name }} \
|
||||
psql -U postgres -d {{ item }} -tAc 'CREATE EXTENSION IF NOT EXISTS postgis;'
|
||||
loop: "{{ postgis.databases }}"
|
||||
register: postgis_activation
|
||||
changed_when: "'CREATE EXTENSION' in postgis_activation.stdout"
|
||||
|
||||
# ⚠ THE HALF THAT MATTERS. Without it, a botched install leaves a green
|
||||
# playbook and an application that fails at its first spatial query — the
|
||||
# symptom would land in Kadans, days later, looking like an app bug.
|
||||
# We ask the database itself, and we FAIL on anything unexpected.
|
||||
- name: Verify PostGIS answers on every database
|
||||
ansible.builtin.shell: |
|
||||
docker exec {{ postgres_container_name }} psql -U postgres -d {{ item }} -tAc \
|
||||
"SELECT postgis_version() || ' | ' || ST_AsGeoJSON(ST_SetSRID(ST_Point(4.83, 45.76), 4326));"
|
||||
loop: "{{ postgis.databases }}"
|
||||
register: postgis_verifier
|
||||
changed_when: false
|
||||
# Not just "the command exited 0": a real geometry must come back with
|
||||
# the SRID applied. A stub that answered an empty string would pass a
|
||||
# bare rc check — and prove nothing.
|
||||
failed_when: >-
|
||||
postgis_verifier.rc != 0
|
||||
or 'USE_GEOS=1' not in postgis_verifier.stdout
|
||||
or '"type":"Point"' not in postgis_verifier.stdout
|
||||
|
||||
- name: Report the PostGIS version in use
|
||||
ansible.builtin.debug:
|
||||
msg: "PostGIS on {{ item.item }} → {{ item.stdout | trim }}"
|
||||
loop: "{{ postgis_verifier.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
|
||||
- name: Create auth_user for pgbouncer (connection pool component)
|
||||
ansible.builtin.shell: |
|
||||
docker exec -it {{ postgres_container_name }} psql -U postgres -d {{ database }} -tc "{{ pg_instruction.replace('$','\$') }}"
|
||||
|
||||
Reference in New Issue
Block a user