mirror of
https://github.com/spantaleev/matrix-docker-ansible-deploy.git
synced 2026-08-29 20:13:13 +00:00
Test matrix-synapse with Molecule
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
---
|
||||
- name: Include roles for Synapse Molecule tests
|
||||
hosts: all
|
||||
become: true
|
||||
vars_files:
|
||||
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
|
||||
gather_facts: true
|
||||
tasks:
|
||||
- name: Include base roles for Synapse Molecule tests
|
||||
ansible.builtin.include_role:
|
||||
name: "{{ role_name }}"
|
||||
public: true
|
||||
loop:
|
||||
- com.devture.ansible.role.playbook_help
|
||||
- com.devture.ansible.role.systemd_docker_base
|
||||
loop_control:
|
||||
loop_var: role_name
|
||||
|
||||
# The role's main task file also carries separately invoked maintenance actions
|
||||
# (media/database imports, user management, and self-checks). Include the same
|
||||
# initialization, validation, and installation files selected by setup-synapse.
|
||||
- name: Include Synapse setup task files
|
||||
ansible.builtin.include_role:
|
||||
name: "custom/{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
|
||||
public: true
|
||||
tasks_from: "{{ role_task_file }}"
|
||||
loop:
|
||||
- init.yml
|
||||
- validate_config.yml
|
||||
- setup_install.yml
|
||||
loop_control:
|
||||
loop_var: role_task_file
|
||||
|
||||
- name: Ensure Synapse is started
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Ensure systemd daemon is reloaded
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Ensure the Synapse systemd service is started
|
||||
ansible.builtin.systemd_service:
|
||||
name: matrix-synapse.service
|
||||
state: started
|
||||
@@ -0,0 +1,114 @@
|
||||
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
options:
|
||||
requirements-file: requirements.yml
|
||||
force: true
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: synapse-${MOLECULE_DISTRO:-ubuntu2604}-default
|
||||
image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2604}-ansible:latest"
|
||||
command: ${MOLECULE_DOCKER_COMMAND:-""}
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||
cgroupns_mode: host
|
||||
privileged: true
|
||||
pre_build_image: true
|
||||
provisioner:
|
||||
name: ansible
|
||||
config_options:
|
||||
defaults:
|
||||
callback_result_format: yaml
|
||||
inventory:
|
||||
group_vars:
|
||||
all:
|
||||
# This first scenario deliberately exercises one main Synapse process. Workers and
|
||||
# everything they bring with them belong in a separate topology-focused scenario.
|
||||
matrix_synapse_workers_enabled: false
|
||||
matrix_synapse_redis_enabled: false
|
||||
matrix_synapse_reverse_proxy_companion_enabled: false
|
||||
|
||||
matrix_synapse_username: matrix
|
||||
matrix_synapse_uid: 1234
|
||||
matrix_synapse_gid: 1234
|
||||
matrix_synapse_container_network: synapse-molecule
|
||||
|
||||
# The non-default listener is reached only over the container network. Override the
|
||||
# upstream image's health command as well, because its default assumes port 8008.
|
||||
matrix_synapse_container_client_api_port: 18008
|
||||
matrix_synapse_container_client_api_host_bind_port: ''
|
||||
matrix_synapse_container_master_extra_arguments:
|
||||
- "--health-cmd='curl -fSs http://localhost:18008/health'"
|
||||
matrix_synapse_container_labels_traefik_enabled: false
|
||||
matrix_synapse_container_labels_traefik_hostname: matrix.molecule.local
|
||||
matrix_synapse_systemd_service_post_start_delay_seconds: 0
|
||||
|
||||
# Postgres is the normal deployment path. Distinct database and user names make the
|
||||
# resulting Synapse schema evidence that these exact connection settings were used.
|
||||
matrix_synapse_database_host: matrix-postgres-molecule
|
||||
matrix_synapse_database_port: 5432
|
||||
matrix_synapse_database_user: molecule_synapse
|
||||
matrix_synapse_database_password: molecule_synapse_pg_password_9b742d
|
||||
matrix_synapse_database_database: molecule_synapse
|
||||
matrix_synapse_database_cp_min: 2
|
||||
matrix_synapse_database_cp_max: 4
|
||||
|
||||
# Local scenario secrets only. They never authenticate to an external service.
|
||||
matrix_synapse_macaroon_secret_key: molecule_synapse_macaroon_4b3f891d
|
||||
matrix_synapse_registration_shared_secret: molecule_synapse_registration_27ed51a8
|
||||
matrix_synapse_form_secret: molecule_synapse_form_96a04c7b
|
||||
matrix_synapse_password_config_pepper: molecule_synapse_pepper_c0f371e6
|
||||
|
||||
# These values differ from both role and upstream defaults and are safe to exercise
|
||||
# without introducing any external dependency.
|
||||
matrix_synapse_public_baseurl: http://matrix.molecule.local:18008/
|
||||
matrix_synapse_presence_enabled: false
|
||||
matrix_synapse_max_upload_size_mb: 37
|
||||
matrix_synapse_url_preview_enabled: false
|
||||
matrix_synapse_allow_public_rooms_over_federation: false
|
||||
matrix_synapse_user_directory_search_all_users: true
|
||||
matrix_synapse_user_directory_prefer_local_users: true
|
||||
matrix_synapse_trusted_key_servers: []
|
||||
matrix_synapse_suppress_key_server_warning: true
|
||||
|
||||
# Keep the scenario fully local and intentionally exclude optional integrations.
|
||||
matrix_synapse_federation_enabled: false
|
||||
matrix_synapse_federation_port_openid_resource_required: false
|
||||
matrix_synapse_tls_federation_listener_enabled: false
|
||||
matrix_synapse_metrics_enabled: false
|
||||
matrix_synapse_email_enabled: false
|
||||
matrix_synapse_matrix_authentication_service_enabled: false
|
||||
matrix_authentication_service_migration_in_progress: false
|
||||
matrix_synapse_ext_synapse_s3_storage_provider_enabled: false
|
||||
matrix_s3_media_store_enabled: false
|
||||
matrix_playbook_ssl_enabled: false
|
||||
|
||||
# verify.yml is a separate play, so pin paths that otherwise come from role defaults.
|
||||
# The component version is deliberately loaded from defaults/main.yml instead.
|
||||
matrix_synapse_base_path: /matrix/synapse
|
||||
matrix_synapse_config_dir_path: /matrix/synapse/config
|
||||
matrix_synapse_storage_path: /matrix/synapse/storage
|
||||
matrix_synapse_media_store_path: /matrix/synapse/storage/media-store
|
||||
env:
|
||||
# Workaround for https://github.com/ansible/molecule/issues/4391
|
||||
ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/../..:/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:${ANSIBLE_HOME:-~/.ansible}/roles
|
||||
scenario:
|
||||
test_sequence:
|
||||
- dependency
|
||||
- cleanup
|
||||
- destroy
|
||||
- syntax
|
||||
- create
|
||||
- prepare
|
||||
- converge
|
||||
- idempotence
|
||||
- verify
|
||||
- cleanup
|
||||
- destroy
|
||||
verifier:
|
||||
name: ansible
|
||||
@@ -0,0 +1,117 @@
|
||||
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
---
|
||||
- name: Prepare Synapse Molecule tests
|
||||
hosts: all
|
||||
become: true
|
||||
vars_files:
|
||||
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
|
||||
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
|
||||
gather_facts: true
|
||||
tasks:
|
||||
- name: Ensure apt cache is updated
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 600
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Ensure required packages are installed
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- python3-requests
|
||||
- fuse-overlayfs
|
||||
state: present
|
||||
|
||||
# The production Synapse unit uses slave bind propagation for its media store. The
|
||||
# privileged Molecule host must expose a shared parent mount to its nested Docker daemon.
|
||||
- name: Ensure nested Docker can use Synapse media-store bind propagation
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- mount
|
||||
- --make-rshared
|
||||
- /
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure Docker is installed
|
||||
ansible.builtin.include_role:
|
||||
name: ansible-role-docker
|
||||
vars:
|
||||
docker_daemon_options:
|
||||
storage-driver: fuse-overlayfs
|
||||
|
||||
- name: Ensure the matrix group exists
|
||||
ansible.builtin.group:
|
||||
name: "{{ matrix_group_name }}"
|
||||
gid: "{{ matrix_user_gid }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure the matrix user exists
|
||||
ansible.builtin.user:
|
||||
name: "{{ matrix_user_name }}"
|
||||
uid: "{{ matrix_user_uid }}"
|
||||
group: "{{ matrix_group_name }}"
|
||||
create_home: false
|
||||
system: true
|
||||
state: present
|
||||
|
||||
- name: Ensure the base data path exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ matrix_base_data_path }}"
|
||||
state: directory
|
||||
owner: "{{ matrix_user_name }}"
|
||||
group: "{{ matrix_group_name }}"
|
||||
mode: '0750'
|
||||
|
||||
# Postgres must attach before the role creates this network during converge.
|
||||
- name: Ensure the Synapse container network exists
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- network
|
||||
- create
|
||||
- "{{ matrix_synapse_container_network }}"
|
||||
register: matrix_synapse_molecule_network
|
||||
changed_when: matrix_synapse_molecule_network.rc == 0
|
||||
failed_when:
|
||||
- matrix_synapse_molecule_network.rc != 0
|
||||
- "'already exists' not in matrix_synapse_molecule_network.stderr"
|
||||
|
||||
- name: Ensure Postgres is running
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/postgres.yml"
|
||||
vars:
|
||||
molecule_shared_postgres_network: "{{ matrix_synapse_container_network }}"
|
||||
molecule_shared_postgres_database: "{{ matrix_synapse_database_database }}"
|
||||
molecule_shared_postgres_username: "{{ matrix_synapse_database_user }}"
|
||||
molecule_shared_postgres_password: "{{ matrix_synapse_database_password }}"
|
||||
|
||||
# Synapse intentionally refuses databases with locale-dependent collation. Recreate the
|
||||
# helper's empty scenario database with the production-required C locale before startup.
|
||||
- name: Remove the locale-dependent empty Synapse database
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- matrix-postgres-molecule
|
||||
- psql
|
||||
- --username={{ matrix_synapse_database_user }}
|
||||
- --dbname=postgres
|
||||
- --command=DROP DATABASE IF EXISTS "{{ matrix_synapse_database_database }}"
|
||||
changed_when: true
|
||||
|
||||
- name: Create the empty Synapse database with C collation
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- matrix-postgres-molecule
|
||||
- psql
|
||||
- --username={{ matrix_synapse_database_user }}
|
||||
- --dbname=postgres
|
||||
- >-
|
||||
--command=CREATE DATABASE "{{ matrix_synapse_database_database }}"
|
||||
OWNER "{{ matrix_synapse_database_user }}" TEMPLATE template0
|
||||
LC_COLLATE 'C' LC_CTYPE 'C'
|
||||
changed_when: true
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../molecule-shared/requirements.yml
|
||||
@@ -0,0 +1,360 @@
|
||||
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
---
|
||||
# Proves a single main Synapse process starts on the non-default listener the role
|
||||
# rendered, serves real Matrix APIs, migrates the configured Postgres database, and
|
||||
# carries no worker, Redis, S3, MAS, email, or other optional-integration topology.
|
||||
- name: Verify Synapse
|
||||
hosts: all
|
||||
become: true
|
||||
vars_files:
|
||||
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
|
||||
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
|
||||
gather_facts: false
|
||||
vars:
|
||||
matrix_synapse_config: "{{ matrix_synapse_config_file.content | b64decode | from_yaml }}"
|
||||
matrix_synapse_http_listeners: >-
|
||||
{{ matrix_synapse_config.listeners | selectattr('type', 'equalto', 'http') | list }}
|
||||
matrix_synapse_http_listener: "{{ matrix_synapse_http_listeners | first | default({}) }}"
|
||||
|
||||
tasks:
|
||||
# The image version comes from the role, not from the scenario, so an assertion cannot
|
||||
# remain green merely because both the scenario and runtime are pinned to the same typo.
|
||||
- name: Load the role's defaults under a separate name
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
|
||||
name: matrix_synapse_role_defaults
|
||||
|
||||
- name: Wait for the Synapse service to become active
|
||||
ansible.builtin.systemd_service:
|
||||
name: matrix-synapse.service
|
||||
register: matrix_synapse_service
|
||||
until: matrix_synapse_service.status.ActiveState == 'active'
|
||||
retries: 30
|
||||
delay: 5
|
||||
failed_when: false
|
||||
|
||||
- name: Assert the Synapse service is stable
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_service.status.ActiveState == 'active'
|
||||
- matrix_synapse_service.status.NRestarts is defined
|
||||
- matrix_synapse_service.status.NRestarts | int == 0
|
||||
fail_msg: >-
|
||||
matrix-synapse.service is
|
||||
{{ matrix_synapse_service.status.ActiveState | default('unknown') }} after
|
||||
{{ matrix_synapse_service.status.NRestarts | default('?') }} restart(s)
|
||||
success_msg: "matrix-synapse.service is active and has not restarted"
|
||||
|
||||
# Probe over the role's own network. No host port is published, matching deployment.
|
||||
- name: Request the Synapse health endpoint
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- run
|
||||
- --rm
|
||||
- --network={{ matrix_synapse_container_network }}
|
||||
- "{{ molecule_shared_image_curl }}"
|
||||
- --silent
|
||||
- --show-error
|
||||
- --write-out
|
||||
- "\nHTTP_STATUS=%{http_code}"
|
||||
- http://matrix-synapse:{{ matrix_synapse_container_client_api_port }}/health
|
||||
register: matrix_synapse_health
|
||||
changed_when: false
|
||||
until: "'HTTP_STATUS=200' in matrix_synapse_health.stdout"
|
||||
retries: 24
|
||||
delay: 5
|
||||
failed_when: false
|
||||
|
||||
- name: Request the Synapse Matrix versions endpoint
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- run
|
||||
- --rm
|
||||
- --network={{ matrix_synapse_container_network }}
|
||||
- "{{ molecule_shared_image_curl }}"
|
||||
- --silent
|
||||
- --show-error
|
||||
- --write-out
|
||||
- "\nHTTP_STATUS=%{http_code}"
|
||||
- http://matrix-synapse:{{ matrix_synapse_container_client_api_port }}/_matrix/client/versions
|
||||
register: matrix_synapse_versions_response
|
||||
changed_when: false
|
||||
until: "'HTTP_STATUS=200' in matrix_synapse_versions_response.stdout"
|
||||
retries: 24
|
||||
delay: 5
|
||||
failed_when: false
|
||||
|
||||
- name: Assert Synapse serves its health endpoint
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_health.rc == 0
|
||||
- "'HTTP_STATUS=200' in matrix_synapse_health.stdout"
|
||||
- matrix_synapse_health.stdout.startswith('OK')
|
||||
fail_msg: >-
|
||||
Synapse did not serve /health on port
|
||||
{{ matrix_synapse_container_client_api_port }}
|
||||
success_msg: "Synapse serves /health"
|
||||
|
||||
- name: Assert Synapse serves its Matrix versions endpoint
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_versions_response.rc == 0
|
||||
- "'HTTP_STATUS=200' in matrix_synapse_versions_response.stdout"
|
||||
fail_msg: >-
|
||||
Synapse did not serve /_matrix/client/versions on port
|
||||
{{ matrix_synapse_container_client_api_port }}
|
||||
success_msg: "Synapse serves /_matrix/client/versions"
|
||||
|
||||
- name: Parse the Matrix versions response
|
||||
ansible.builtin.set_fact:
|
||||
matrix_synapse_versions: >-
|
||||
{{ matrix_synapse_versions_response.stdout
|
||||
| regex_replace('\nHTTP_STATUS=200$', '')
|
||||
| from_json }}
|
||||
|
||||
- name: Assert the versions response is a real Matrix API document
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_versions.versions is sequence
|
||||
- matrix_synapse_versions.versions | length > 0
|
||||
- "'v1.1' in matrix_synapse_versions.versions"
|
||||
- matrix_synapse_versions.unstable_features is mapping
|
||||
fail_msg: "The versions endpoint did not return a Matrix versions document"
|
||||
success_msg: "The versions endpoint returns a Matrix versions document"
|
||||
|
||||
# The role moved the listener away from 8008. Refusing the upstream default port proves
|
||||
# the successful requests above did not accidentally hit an unchanged default listener.
|
||||
- name: Request the upstream-default Synapse port
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- run
|
||||
- --rm
|
||||
- --network={{ matrix_synapse_container_network }}
|
||||
- "{{ molecule_shared_image_curl }}"
|
||||
- --silent
|
||||
- --output
|
||||
- /dev/null
|
||||
- --connect-timeout
|
||||
- '2'
|
||||
- --max-time
|
||||
- '3'
|
||||
- http://matrix-synapse:8008/health
|
||||
register: matrix_synapse_default_listener
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Assert Synapse does not listen on the upstream-default port
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_default_listener.rc != 0
|
||||
fail_msg: "Synapse still answers on port 8008, so the non-default listener is not proven"
|
||||
success_msg: "Only the role-configured client listener answers"
|
||||
|
||||
- name: Read the homeserver configuration the role rendered
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ matrix_synapse_config_dir_path }}/homeserver.yaml"
|
||||
register: matrix_synapse_config_file
|
||||
|
||||
- name: Read the homeserver configuration identity
|
||||
ansible.builtin.stat:
|
||||
path: "{{ matrix_synapse_config_dir_path }}/homeserver.yaml"
|
||||
register: matrix_synapse_config_stat
|
||||
|
||||
- name: Assert the parsed homeserver identity and behavior
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config.server_name == matrix_domain
|
||||
- matrix_synapse_config.public_baseurl == matrix_synapse_public_baseurl
|
||||
- matrix_synapse_config.max_upload_size == (matrix_synapse_max_upload_size_mb | string) + 'M'
|
||||
- not matrix_synapse_config.presence.enabled
|
||||
- not matrix_synapse_config.url_preview_enabled
|
||||
- not matrix_synapse_config.allow_public_rooms_over_federation
|
||||
- matrix_synapse_config.user_directory.search_all_users
|
||||
- matrix_synapse_config.user_directory.prefer_local_users
|
||||
- matrix_synapse_config.trusted_key_servers == []
|
||||
fail_msg: "The parsed homeserver configuration does not carry the scenario's values"
|
||||
success_msg: "The parsed homeserver configuration carries the scenario's values"
|
||||
|
||||
- name: Assert the parsed homeserver secrets
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config.macaroon_secret_key == matrix_synapse_macaroon_secret_key
|
||||
- matrix_synapse_config.registration_shared_secret == matrix_synapse_registration_shared_secret
|
||||
- matrix_synapse_config.form_secret == matrix_synapse_form_secret
|
||||
- matrix_synapse_config.password_config.pepper == matrix_synapse_password_config_pepper
|
||||
fail_msg: "The parsed homeserver configuration does not carry the scenario's secrets"
|
||||
success_msg: "The parsed homeserver configuration carries the scenario's secrets"
|
||||
no_log: true
|
||||
|
||||
- name: Assert the parsed client listener topology
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config.listeners | length == 1
|
||||
- matrix_synapse_http_listeners | length == 1
|
||||
- matrix_synapse_http_listener.port == matrix_synapse_container_client_api_port
|
||||
- not matrix_synapse_http_listener.tls
|
||||
- matrix_synapse_http_listener.bind_addresses == ['::']
|
||||
- matrix_synapse_http_listener.resources | length == 1
|
||||
- matrix_synapse_http_listener.resources[0].names == ['client']
|
||||
fail_msg: "The parsed homeserver configuration has an unexpected listener topology"
|
||||
success_msg: "The parsed homeserver configuration has one non-default client listener"
|
||||
|
||||
- name: Assert the parsed Postgres configuration
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config.database.name == 'psycopg2'
|
||||
- matrix_synapse_config.database.args.host == matrix_synapse_database_host
|
||||
- matrix_synapse_config.database.args.port == matrix_synapse_database_port
|
||||
- matrix_synapse_config.database.args.user == matrix_synapse_database_user
|
||||
- matrix_synapse_config.database.args.password == matrix_synapse_database_password
|
||||
- matrix_synapse_config.database.args.database == matrix_synapse_database_database
|
||||
- matrix_synapse_config.database.args.cp_min == matrix_synapse_database_cp_min
|
||||
- matrix_synapse_config.database.args.cp_max == matrix_synapse_database_cp_max
|
||||
fail_msg: "The parsed homeserver configuration does not point at scenario Postgres"
|
||||
success_msg: "The parsed homeserver configuration points at scenario Postgres"
|
||||
no_log: true
|
||||
|
||||
- name: Assert optional Synapse topology is absent
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not matrix_synapse_config.redis.enabled
|
||||
- matrix_synapse_config.media_storage_providers == []
|
||||
- matrix_synapse_config.modules == []
|
||||
- "'matrix_authentication_service' not in matrix_synapse_config"
|
||||
- "'email' not in matrix_synapse_config"
|
||||
- "'worker_app' not in matrix_synapse_config"
|
||||
fail_msg: "An excluded worker, Redis, S3, MAS, email, or module integration is configured"
|
||||
success_msg: "The configuration contains only the intended main-process topology"
|
||||
|
||||
- name: Assert the homeserver configuration file identity
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config_stat.stat.uid | int == matrix_user_uid | int
|
||||
- matrix_synapse_config_stat.stat.gid | int == matrix_user_gid | int
|
||||
- matrix_synapse_config_stat.stat.mode == '0644'
|
||||
fail_msg: "homeserver.yaml has the wrong ownership or mode"
|
||||
success_msg: "homeserver.yaml has the playbook identity and expected mode"
|
||||
|
||||
# These tables appear only after hostname resolution, authentication, and genuine Synapse
|
||||
# migrations against the exact non-default database prepared for the scenario.
|
||||
- name: List the tables Synapse created in Postgres
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- matrix-postgres-molecule
|
||||
- psql
|
||||
- --username={{ matrix_synapse_database_user }}
|
||||
- --dbname={{ matrix_synapse_database_database }}
|
||||
- --tuples-only
|
||||
- --no-align
|
||||
- --command=SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
||||
register: matrix_synapse_tables
|
||||
changed_when: false
|
||||
|
||||
- name: Assert Synapse migrated its Postgres schema
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_tables.rc == 0
|
||||
- "'schema_version' in matrix_synapse_table_names"
|
||||
- "'users' in matrix_synapse_table_names"
|
||||
- "'rooms' in matrix_synapse_table_names"
|
||||
- "'events' in matrix_synapse_table_names"
|
||||
- "'state_groups' in matrix_synapse_table_names"
|
||||
- matrix_synapse_table_names | length > 50
|
||||
fail_msg: >-
|
||||
Synapse did not migrate its schema in {{ matrix_synapse_database_database }}
|
||||
(found {{ matrix_synapse_table_names | length }} table(s))
|
||||
success_msg: "Synapse genuinely migrated the configured Postgres database"
|
||||
vars:
|
||||
matrix_synapse_table_names: "{{ matrix_synapse_tables.stdout_lines | select | list }}"
|
||||
|
||||
- name: Inspect the running Synapse container
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- container
|
||||
- inspect
|
||||
- matrix-synapse
|
||||
register: matrix_synapse_container_inspect
|
||||
changed_when: false
|
||||
|
||||
- name: Parse the running Synapse container inspection
|
||||
ansible.builtin.set_fact:
|
||||
matrix_synapse_container: "{{ (matrix_synapse_container_inspect.stdout | from_json) | first }}"
|
||||
|
||||
- name: Assert the running container uses the exact pinned image
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container.Config.Image == 'ghcr.io/element-hq/synapse:' + matrix_synapse_role_defaults.matrix_synapse_version
|
||||
fail_msg: "The running container does not use the exact image defaults/main.yml pins"
|
||||
success_msg: "The running container uses the exact image defaults/main.yml pins"
|
||||
|
||||
- name: Assert the running container uses the scenario identity
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container.Config.User == (matrix_user_uid | string) + ':' + (matrix_user_gid | string)
|
||||
fail_msg: "The running container does not use the playbook-supplied identity"
|
||||
success_msg: "The running container uses the playbook-supplied identity"
|
||||
|
||||
- name: Assert the running container security isolation
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container.HostConfig.ReadonlyRootfs
|
||||
- "'ALL' in matrix_synapse_container.HostConfig.CapDrop"
|
||||
fail_msg: "The running container does not have read-only, capability-dropped isolation"
|
||||
success_msg: "The running container has read-only, capability-dropped isolation"
|
||||
|
||||
- name: Assert the running container has a read-only config mount
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_config_mount | length > 0
|
||||
- not matrix_synapse_config_mount.RW | default(true)
|
||||
fail_msg: "The running container does not mount its configuration read-only"
|
||||
success_msg: "The running container mounts its configuration read-only"
|
||||
vars:
|
||||
matrix_synapse_config_mount: >-
|
||||
{{ matrix_synapse_container.Mounts
|
||||
| selectattr('Destination', 'equalto', '/data')
|
||||
| first | default({}) }}
|
||||
|
||||
- name: Assert the running container has a writable storage mount
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_storage_mount | length > 0
|
||||
- matrix_synapse_storage_mount.RW | default(false)
|
||||
fail_msg: "The running container does not mount its media storage writable"
|
||||
success_msg: "The running container mounts its media storage writable"
|
||||
vars:
|
||||
matrix_synapse_storage_mount: >-
|
||||
{{ matrix_synapse_container.Mounts
|
||||
| selectattr('Destination', 'equalto', '/matrix-media-store-parent')
|
||||
| first | default({}) }}
|
||||
|
||||
- name: Assert the running container is healthy
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container.State.Health.Status == 'healthy'
|
||||
fail_msg: "The running container is not healthy"
|
||||
success_msg: "The running container is healthy"
|
||||
|
||||
- name: Assert the running container has only its private network
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container_network in matrix_synapse_container.NetworkSettings.Networks
|
||||
- matrix_synapse_container.NetworkSettings.Networks | length == 1
|
||||
fail_msg: "The running container has unexpected network attachments"
|
||||
success_msg: "The running container has only its private network"
|
||||
|
||||
- name: Assert the running container publishes no host ports
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- matrix_synapse_container.HostConfig.PortBindings | default({}, true) | length == 0
|
||||
fail_msg: "The running container unexpectedly publishes a host port"
|
||||
success_msg: "The running container publishes no host ports"
|
||||
Reference in New Issue
Block a user