2 Commits

10 changed files with 195 additions and 64 deletions

View File

@ -1,12 +1,13 @@
namespace: finallycoffee namespace: finallycoffee
name: databases name: databases
version: 0.1.1 version: 0.1.2
readme: README.md readme: README.md
authors: authors:
- transcaffeine <transcaffeine@finally.coffee> - transcaffeine <transcaffeine@finally.coffee>
description: Collection for deploying and configuring databases description: Collection for deploying and configuring databases
dependencies: dependencies:
"community.docker": "^3.0.0" "community.docker": "^4.0.0"
"community.postgresql": "^3.9.0"
license_file: LICENSE.md license_file: LICENSE.md
build_ignore: build_ignore:
- '*.tar.gz' - '*.tar.gz'

View File

@ -2,3 +2,26 @@
PostgreSQL is the self proclaimed "world's most advanced" open source relational PostgreSQL is the self proclaimed "world's most advanced" open source relational
database. This ansible role can deploy and configure postgresql. database. This ansible role can deploy and configure postgresql.
By default, the role configures the remote's effective ansible user with
peer authentication for the (postgresql) role `postgres` on all databases (with all grants).
## Required configuration
Set `postgresql_superuser_password` to your superusers desired password.
## Optional configuration
Set `postgresql_major_version` to your desired postgresql major version,
for supported major versions see [`defaults/main/main.yml`](defaults/main/main.yml#L6).
This role can be executed multiple times with different
`postgresql_major_version` values to provide new database versions for up-to-
date applications and older versions for software which does not yet support
them. Container name and host mounts encode the major version to prevent
accidental usage of the 'wrong' `PGDATA` directory.
## Requirements
- `psycopg2` (pip) package
- `docker` (pip) package

View File

@ -8,7 +8,7 @@ postgresql_config_port: 5432
postgresql_base_config: postgresql_base_config:
listen_addresses: "{{ postgresql_config_listen_addresses }}" listen_addresses: "{{ postgresql_config_listen_addresses }}"
connect_socket: "{{ postgresql_config_connect_socket }}" unix_socket_directories: "{{ postgresql_config_unix_socket_directories }}"
port: "{{ postgresql_config_port }}" port: "{{ postgresql_config_port }}"
postgresql_merged_config: >-2 postgresql_merged_config: >-2
{{ postgresql_base_config | combine( {{ postgresql_base_config | combine(

View File

@ -38,6 +38,28 @@ postgresql_container_etc_hosts: ~
postgresql_container_restart_policy: "on-failure" postgresql_container_restart_policy: "on-failure"
postgresql_container_state: >-2 postgresql_container_state: >-2
{{ (postgresql_state == 'present') | ternary('started', 'absent') }} {{ (postgresql_state == 'present') | ternary('started', 'absent') }}
postgresql_container_volumes: ~
postgresql_container_unix_socket_path: >-2
{{ postgresql_config_unix_socket_directories | first }}
postgresql_container_base_volumes:
- "{{ postgresql_container_passwd_file }}:/etc/passwd:ro"
- "{{ postgresql_data_path }}:{{ postgresql_container_data_dir }}:Z"
postgresql_container_config_volumes:
- "{{ postgresql_pg_hba_conf_file }}:{{ postgresql_container_data_dir }}/pg_hba.conf:ro"
- "{{ postgresql_pg_ident_conf_file }}:{{ postgresql_container_data_dir }}/pg_ident.conf:ro"
postgresql_container_unix_socket_volumes:
- "{{ postgresql_container_unix_socket_path }}:{{ postgresql_container_unix_socket_path }}:rw,rshared"
postgresql_container_initdb_volumes: >-2
{{ postgresql_container_base_volumes
+ postgresql_container_unix_socket_volumes
+ (postgresql_container_volumes | default([], true)) }}
postgresql_container_merged_volumes: >-2
{{ postgresql_container_base_volumes
+ postgresql_container_config_volumes
+ (postgresql_container_unix_socket_volumes if postgresql_config_connect_socket else [])
+ (postgresql_container_volumes | default([], true)) }}
postgresql_systemd_tmpfile_socket_correction_unit_name: >-2
{{ postgresql_container_unix_socket_path | split('/') | reject('eq', '') | join('-') }}
# (Memory) performance tuning # (Memory) performance tuning
postgresql_container_memory: ~ postgresql_container_memory: ~
@ -47,4 +69,5 @@ postgresql_container_oom_kill: ~
postgresql_container_oom_score_adj: ~ postgresql_container_oom_score_adj: ~
postgresql_container_ulimits: ~ postgresql_container_ulimits: ~
postgresql_container_passwd_file: "/etc/postgresql/{{ postgresql_major_version }}/passwd" postgresql_container_passwd_file: "{{ postgresql_config_path }}/passwd"
postgresql_container_data_dir: "/var/lib/postgresql/data"

View File

@ -1,7 +1,7 @@
--- ---
postgresql_user: postgresql postgresql_user: postgresql
postgresql_version: >-2 postgresql_version: >-2
{{ postgresql_version[postgres_major_version | string] }} {{ postgresql_versions[postgresql_major_version | string] }}
postgresql_major_version: 16 postgresql_major_version: 16
postgresql_versions: postgresql_versions:
"17": "17.2" "17": "17.2"
@ -14,12 +14,19 @@ postgresql_config_path: >-2
postgresql_data_path: >-2 postgresql_data_path: >-2
/var/lib/postgresql/{{ postgresql_major_version }} /var/lib/postgresql/{{ postgresql_major_version }}
postgresql_pg_ident_conf_file: >-2 postgresql_pg_ident_conf_file: >-2
{{ postgresql_data_path }}/pg_ident.conf {{ postgresql_config_path }}/pg_ident.conf
postgresql_pg_hba_conf_file: >-2 postgresql_pg_hba_conf_file: >-2
{{ postgresql_data_path }}/pg_hba.conf {{ postgresql_config_path }}/pg_hba.conf
postgresql_admin_role: "{{ postgresql_user }}" postgresql_admin_role: "postgres"
postgresql_admin_role_contype: local postgresql_admin_role_contype: local
postgresql_admin_role_method: peer postgresql_admin_role_method: peer
postgresql_admin_local_user: >-2
{{ ansible_facts['user_id'] }}
postgresql_admin_role_mapping_name: >-2
{{ postgresql_admin_local_user }}_{{ postgresql_admin_role }}
postgresql_admin_pg_ident_conf: "{{ postgresql_admin_role_mapping_name }}\t{{ postgresql_admin_local_user }}\t{{ postgresql_admin_role }}"
postgresql_admin_pg_hba_conf_options: >-2
map={{ postgresql_admin_role_mapping_name }}
postgresql_superuser_password: ~ postgresql_superuser_password: ~
postgresql_state: present postgresql_state: present

View File

@ -1,49 +1,60 @@
--- ---
- name: Ensure postgresql superuser is set - name: Configure postgresql
community.postgresql.postgresql_user: block:
name: "{{ postgresql_admin_role }}" - name: Ensure postgresql superuser is set
password: "{{ postgresql_superuser_password }}" community.postgresql.postgresql_user:
login_host: >-2 name: "{{ postgresql_admin_role }}"
password: "{{ postgresql_superuser_password }}"
login_host: "{{ postgresql_login_host }}"
register: postgresql_superuser_password_result
until: "postgresql_superuser_password_result is succeeded"
retries: 10
delay: 2
- name: Ensure postgresql configuration is set
community.postgresql.postgresql_set:
name: "{{ option.key }}"
value: "{{ pg_option_value }}"
login_host: "{{ postgresql_login_host }}"
login_port: "{{ postgresql_config_port }}"
login_password: "{{ postgresql_superuser_password }}"
loop: "{{ postgresql_merged_config | dict2items }}"
loop_control:
loop_var: option
vars:
pg_option_value: >-2
{{
(option.value | join(' '))
if (option.value is iterable
and option.value is not string
and option.value is not mapping)
else option.value
}}
register: postgresql_config_results
- name: Ensure postgresql configuration is reloaded
community.postgresql.postgresql_query:
db: "postgres"
query: "SELECT pg_reload_conf();"
login_host: "{{ postgresql_login_host }}"
login_port: "{{ postgresql_config_port }}"
login_password: "{{ postgresql_superuser_password }}"
- name: Ensure restart handler is fired if required
debug:
msg: "{{ result.option.key }} changed! Restart required: {{ result.restart_required }}"
when: result.changed
changed_when: "{{ result.restart_required }}"
notify: postgresql_restart
loop: "{{ postgresql_config_results.results }}"
loop_control:
loop_var: result
label: "{{ result.option.key }}"
when: postgresql_state == 'present'
vars:
postgresql_login_host: >-2
{{ {{
(postgresql_config_unix_socket_directories | first) (postgresql_config_unix_socket_directories | first)
if postgresql_config_connect_socket else if postgresql_config_connect_socket else
(postgresql_container_info.container.NetworkSettings.IPAddress) (postgresql_container_info.container.NetworkSettings.IPAddress)
}} }}
register: postgresql_superuser_password_result
until: "postgresql_superuser_password_result is succeeded"
retries: 10
delay: 2
- name: Ensure postgresql configuration is set
community.postgresql.postgresql_set:
name: "{{ option.key }}"
value: "{{ option.value }}"
login_host: >-2
{{
(postgresql_config_unix_socket_directories | first)
if postgresql_config_connect_socket else
(postgresql_container_info.container.NetworkSettings.IPAddress)
}}
login_port: "{{ postgresql_config_port }}"
login_password: #TODO
loop: "{{ postgresql_merged_options | dict2items }}"
loop_control:
loop_var: option
- name: Ensure postgresql configuration is reloaded
community.postgresql.postgresql_query:
query: "SELECT pg_reload_conf();"
login_host: #TODO
login_port: #TODO
login_password: #TODO
- name: Ensure restart handler is fired if required
debug:
msg: "{{ result.option.key }} changed! Restart required: {{ result.restart_required }}"
when: result.changed
changed_when: "{{ result.restart_required }}"
notify: postgresql_restart
loop: "{{ postgresql_config_results }}"
loop_control:
loop_var: result
label: "{{ result.option.name }}"

View File

@ -19,11 +19,59 @@
mode: "0640" mode: "0640"
when: postgresql_state == 'present' when: postgresql_state == 'present'
- name: Ensure systemd unit to correct path permissions is {{ postgresql_state }}
ansible.builtin.copy:
dest: "/etc/systemd/system/{{ postgresql_systemd_tmpfile_socket_correction_unit_name }}.service"
content: |+2
[Unit]
Description="Ensure permissions on {{ postgresql_container_unix_socket_path }}"
After=systemd-tmpfiles-setup.service
Before=docker.service
[Service]
Type=exec
RemainAfterExit=yes
ExecStart=/bin/bash -c 'mkdir {{ postgresql_container_unix_socket_path }} ||:; chown {{ postgresql_user }}:{{ postgresql_user }} {{ postgresql_container_unix_socket_path }}'
[Install]
WantedBy=multi-user.target
when:
- ansible_facts['service_mgr'] == 'systemd'
- postgresql_state == 'present'
register: postgresql_systemd_tmpfile_correction_unit_info
- name: Ensure systemd is reloaded
ansible.builtin.systemd:
daemon_reload: true
when:
- postgresql_systemd_tmpfile_correction_unit_info.changed
- name: Ensure systemd unit {{ postgresql_systemd_tmpfile_socket_correction_unit_name }} is {{ postgresql_container_state }}
ansible.builtin.systemd:
name: "{{ postgresql_systemd_tmpfile_socket_correction_unit_name }}.service"
state: "{{ postgresql_container_state }}"
when: ansible_facts['service_mgr'] == 'systemd'
- name: Ensure systemd unit {{ postgresql_systemd_tmpfile_socket_correction_unit_name }} is {{ postgresql_container_state }}
ansible.builtin.systemd:
name: "{{ postgresql_systemd_tmpfile_socket_correction_unit_name }}.service"
enabled: "{{ postgresql_state == 'present' }}"
when: ansible_facts['service_mgr'] == 'systemd'
- name: Lookup {{ postgresql_data_path }}/global
ansible.builtin.stat:
path: "{{ postgresql_data_path }}/global"
get_checksum: false
register: postgresql_global_data_info
- name: Initialize database if empty - name: Initialize database if empty
ansible.builtin.include_tasks: ansible.builtin.include_tasks:
file: "docker-initialize.yml" file: "initialize-docker.yml"
when: when:
- postgresql_state == 'present' - postgresql_state == 'present'
- not postgresql_global_data_info.stat.exists
- postgresql_global_data_info.stat.isdir is defined
- not postgresql_global_data_info.stat.isdir
- name: Ensure postgresql container '{{ postgresql_container_name }}' is {{ postgresql_container_state }} - name: Ensure postgresql container '{{ postgresql_container_name }}' is {{ postgresql_container_state }}
community.docker.docker_container: community.docker.docker_container:
@ -33,6 +81,7 @@
user: "{{ postgresql_container_user | default(omit, true) }}" user: "{{ postgresql_container_user | default(omit, true) }}"
ports: "{{ postgresql_container_ports | default(omit, true) }}" ports: "{{ postgresql_container_ports | default(omit, true) }}"
labels: "{{ postgresql_container_labels | default(omit, true) }}" labels: "{{ postgresql_container_labels | default(omit, true) }}"
volumes: "{{ postgresql_container_merged_volumes }}"
recreate: "{{ postgresql_container_recreate | default(omit, true) }}" recreate: "{{ postgresql_container_recreate | default(omit, true) }}"
networks: "{{ postgresql_container_networks | default(omit, true) }}" networks: "{{ postgresql_container_networks | default(omit, true) }}"
etc_hosts: "{{ postgresql_container_etc_hosts | default(omit, true) }}" etc_hosts: "{{ postgresql_container_etc_hosts | default(omit, true) }}"
@ -44,5 +93,3 @@
ulimits: "{{ postgresql_container_ulimits | default(omit, true) }}" ulimits: "{{ postgresql_container_ulimits | default(omit, true) }}"
restart_policy: "{{ postgresql_container_restart_policy | default(omit, true) }}" restart_policy: "{{ postgresql_container_restart_policy | default(omit, true) }}"
state: "{{ postgresql_container_state }}" state: "{{ postgresql_container_state }}"
-

View File

@ -1,21 +1,34 @@
---
- name: Ensure container '{{ postgresql_container_name }}' is {{ postgresql_container_state }} to initialise the database - name: Ensure container '{{ postgresql_container_name }}' is {{ postgresql_container_state }} to initialise the database
community.docker.docker_container: community.docker.docker_container:
name: "{{ postgresql_container_name }}" name: "{{ postgresql_container_name }}"
user: "{{ postgresql_container_user }}"
image: "{{ postgresql_container_image }}" image: "{{ postgresql_container_image }}"
ports: "{{ postgresql_container_ports }}" env: >-2
{{ postgresql_container_env | default({}, true)
| combine({'POSTGRES_PASSWORD': postgresql_superuser_password}) }}
user: "{{ postgresql_container_user | default(omit, true) }}"
ports: "{{ postgresql_container_ports | default(omit, true) }}"
labels: "{{ postgresql_container_labels | default(omit, true) }}" labels: "{{ postgresql_container_labels | default(omit, true) }}"
volumes: "{{ postgresql_container_initdb_volumes }}"
recreate: "{{ postgresql_container_recreate | default(omit, true) }}"
networks: "{{ postgresql_container_networks | default(omit, true) }}" networks: "{{ postgresql_container_networks | default(omit, true) }}"
etc_hosts: "{{ postgresql_container_etc_hosts | default(omit, true) }}" etc_hosts: "{{ postgresql_container_etc_hosts | default(omit, true) }}"
state: started memory: "{{ postgresql_container_memory | default(omit, true) }}"
memory_reservation: "{{ postgresql_container_memory_reservation | default(omit, true) }}"
oom_killer: "{{ postgresql_container_oom_killer | default(omit, true) }}"
oom_score_adj: "{{ postgresql_container_oom_score_adj | default(omit, true) }}"
shm_size: "{{ postgresql_container_shm_size | default(omit, true) }}"
ulimits: "{{ postgresql_container_ulimits | default(omit, true) }}"
restart_policy: "{{ postgresql_container_restart_policy | default(omit, true) }}"
state: "{{ postgresql_container_state }}"
register: postgresql_container_info register: postgresql_container_info
- name: Wait for container startup - name: Wait for container startup
block: block:
- name: Wait for container startup (socket) - name: Wait for container startup (socket)
ansible.builtin.wait_for: ansible.builtin.wait_for:
path: "{{ postgresql_config_unix_socket_directories | first }}.s.PGSQL.{{ postgresql_config_port }}" path: "{{ postgresql_config_unix_socket_directories | first }}/.s.PGSQL.{{ postgresql_config_port }}"
when: "{{ postgresql_config_connect_socket }}" when: "postgresql_config_connect_socket | bool"
- name: Wait for container startup (port) - name: Wait for container startup (port)
ansible.builtin.wait_for: ansible.builtin.wait_for:
host: >-2 host: >-2
@ -24,7 +37,7 @@
postgresql_config_listen_addresses | first postgresql_config_listen_addresses | first
) }} ) }}
port: "{{ postgresql_config_port }}" port: "{{ postgresql_config_port }}"
when: "{{ not postgresql_config_connect_socket }}" when: "not postgresql_config_connect_socket | bool"
vars: vars:
pg_addresses: "{{ postgresql_config_listen_addresses | join(',') }}" pg_addresses: "{{ postgresql_config_listen_addresses | join(',') }}"
@ -32,4 +45,3 @@
community.docker.docker_container: community.docker.docker_container:
name: "{{ postgresql_container_name }}" name: "{{ postgresql_container_name }}"
state: absent state: absent

View File

@ -33,6 +33,7 @@
loop: loop:
- name: "{{ postgresql_config_path }}" - name: "{{ postgresql_config_path }}"
- name: "{{ postgresql_data_path }}" - name: "{{ postgresql_data_path }}"
mode: "0700"
loop_control: loop_control:
loop_var: path loop_var: path
label: "{{ path.name }}" label: "{{ path.name }}"
@ -56,7 +57,7 @@
Aborting... Aborting...
when: when:
- postgresql_data_dir_version_info.stat.exists - postgresql_data_dir_version_info.stat.exists
- "(postgresql_data_dir_version_content | b64decode | int) != (postgresql_major_version | int)" - "(postgresql_data_dir_version_content.content | b64decode | int) != (postgresql_major_version | int)"
- name: Prepare authentication and authorization for database admin role - name: Prepare authentication and authorization for database admin role
ansible.builtin.include_tasks: ansible.builtin.include_tasks:
@ -65,3 +66,7 @@
- name: Deploy postgresql using {{ postgresql_deployment_method }} - name: Deploy postgresql using {{ postgresql_deployment_method }}
ansible.builtin.include_tasks: ansible.builtin.include_tasks:
file: "deploy-{{ postgresql_deployment_method }}.yml" file: "deploy-{{ postgresql_deployment_method }}.yml"
- name: Configure postgresql
ansible.builtin.include_tasks:
file: "configure.yml"

View File

@ -20,8 +20,9 @@
line: "# Ansible managed" line: "# Ansible managed"
- name: "{{ postgresql_pg_ident_conf_file }}" - name: "{{ postgresql_pg_ident_conf_file }}"
insert_after: "# Ansible managed" insert_after: "# Ansible managed"
line: "{{ ansible_user }}_{{ postgresql_admin_role }}\t{{ ansible_user }}\t{{ postgresql_admin_role }}" line: "{{ postgresql_admin_pg_ident_conf }}"
when: postgresql_state == 'present' when: postgresql_state == 'present'
notify: postgresql_restart
- name: Configure permissions for postgresql admin role - name: Configure permissions for postgresql admin role
community.postgresql.postgresql_pg_hba: community.postgresql.postgresql_pg_hba:
@ -29,5 +30,6 @@
contype: "{{ postgresql_admin_role_contype }}" contype: "{{ postgresql_admin_role_contype }}"
users: "{{ postgresql_admin_role }}" users: "{{ postgresql_admin_role }}"
method: "{{ postgresql_admin_role_method }}" method: "{{ postgresql_admin_role_method }}"
options: "map={{ ansible_user }}_{{ postgresql_admin_role }}" options: "{{ postgresql_admin_pg_hba_conf_options }}"
when: postgresql_state == 'present' when: postgresql_state == 'present'
notify: postgresql_restart