Files
matrix-docker-ansible-deploy/roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml
T
Slavi PantaleevandClaude Opus 5 f320bfc93b Add a Molecule scenario for mautrix-whatsapp, and a shared playbook context
The second scenario, chosen to be a bridge rather than another HTTP
component: bridges are roughly 40 of the 70 roles here, and validating the
shape on role two is cheaper than discovering it on role forty. It found
two things the first role could not.

Bridges read a whole family of variables from outside themselves -
matrix_bridges_relay_enabled, _encryption_enabled, _encryption_default,
_msc4190_enabled, _self_sign_enabled, plus matrix_admin - all defined in
matrix-base. Since every bridge reads them, they belong in shared context
rather than in each bridge's scenario: molecule-shared/playbook-context.yml
now carries them along with the identity and path variables the first
scenario had inline. Note it is loaded through vars_files, which outranks
inventory group_vars, so it is authoritative; a scenario that wants to prove
one of these reaches the rendered configuration should say so explicitly.

Appservices call /whoami while starting and refuse to run if the id returned
is not the bot user they were configured as - mautrix-whatsapp exits 17 with
"Unexpected user ID in whoami call". The shared stub now takes the id it
should claim to be. Expect every bridge and bot to need this.

What the scenario proves: the bridge starts and stays up, opens its
appservice port, its rendered config and appservice registration carry the
scenario's tokens and bot user, it created its sqlite database under the
role's data path as the role's uid, and it runs the version the role pins.
It does not bridge anything and never will.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:02:53 +03:00

178 lines
8.3 KiB
YAML

# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later
---
- name: Verify matrix-alertmanager-receiver
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
tasks:
# The version is read out of the role's own defaults rather than pinned in
# molecule.yml, so that the assertion further down compares the running
# image against what defaults/main.yml actually ships. Pinning it here
# would make that assertion compare the scenario with itself.
- 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_alertmanager_receiver_role_defaults
- name: Wait for the matrix-alertmanager-receiver service to become active
ansible.builtin.systemd_service:
name: matrix-alertmanager-receiver.service
register: matrix_alertmanager_receiver_service
until: matrix_alertmanager_receiver_service.status.ActiveState == 'active'
retries: 30
delay: 5
failed_when: false
# `Restart=always` means a crash-looping container still reports `active`,
# so the restart counter is checked alongside it. Asserted as `is defined`
# too, because `| int` turns a missing property into 0 and would pass
# vacuously on a systemd that does not expose it.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
- matrix_alertmanager_receiver_service.status.ActiveState == 'active'
- matrix_alertmanager_receiver_service.status.NRestarts is defined
- matrix_alertmanager_receiver_service.status.NRestarts | int == 0
fail_msg: >-
matrix-alertmanager-receiver.service is
{{ matrix_alertmanager_receiver_service.status.ActiveState | default('unknown') }}
after {{ matrix_alertmanager_receiver_service.status.NRestarts | default('?') }}
automatic restart(s)
success_msg: "matrix-alertmanager-receiver.service is active and has not restarted"
# Probed from inside the container network rather than from the host: the
# role publishes no host port, exactly as it does in a real deployment,
# where Traefik reaches it over the network instead.
- name: Wait for matrix-alertmanager-receiver to answer on the port the role configured
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_alertmanager_receiver_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --write-out
- "\nHTTP_STATUS=%{http_code}"
- "http://matrix-alertmanager-receiver:{{ matrix_alertmanager_receiver_config_http_port }}{{ matrix_alertmanager_receiver_config_http_metrics_path }}"
register: matrix_alertmanager_receiver_metrics
changed_when: false
until: "'HTTP_STATUS=200' in matrix_alertmanager_receiver_metrics.stdout"
retries: 24
delay: 5
failed_when: false
# The port and the metrics path are both non-default in this scenario, so a
# 200 here is only reachable if the configuration the role rendered is what
# the process is actually running on.
- name: Assert the configured port and metrics path reached the process
ansible.builtin.assert:
that:
- "'HTTP_STATUS=200' in matrix_alertmanager_receiver_metrics.stdout"
fail_msg: >-
matrix-alertmanager-receiver did not serve metrics on port
{{ matrix_alertmanager_receiver_config_http_port }} at
{{ matrix_alertmanager_receiver_config_http_metrics_path }}
({{ matrix_alertmanager_receiver_metrics.stdout | default('no output') }})
success_msg: >-
matrix-alertmanager-receiver serves metrics on the configured port and path
- name: Assert the metrics endpoint is really Prometheus metrics
ansible.builtin.assert:
that:
- "'# HELP' in matrix_alertmanager_receiver_metrics.stdout"
fail_msg: >-
The metrics endpoint answered, but did not return Prometheus metrics
success_msg: "The metrics endpoint returns Prometheus metrics"
# A negative control for the assertion above: the role's own default metrics
# path must NOT answer, or a 200 on the configured path would prove nothing
# about the configuration having been applied.
- name: Ask for the role's default metrics path, which this scenario moved away from
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_alertmanager_receiver_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --output
- /dev/null
- --write-out
- "HTTP_STATUS=%{http_code}"
- "http://matrix-alertmanager-receiver:{{ matrix_alertmanager_receiver_config_http_port }}/metrics"
register: matrix_alertmanager_receiver_default_path
changed_when: false
failed_when: false
- name: Assert the default metrics path does not answer
ansible.builtin.assert:
that:
- "'HTTP_STATUS=200' not in matrix_alertmanager_receiver_default_path.stdout"
fail_msg: >-
/metrics answered as well, so serving on
{{ matrix_alertmanager_receiver_config_http_metrics_path }} does not
prove the role's configuration reached the process
({{ matrix_alertmanager_receiver_default_path.stdout | default('no output') }})
success_msg: "Only the configured metrics path answers"
- name: Read the configuration file the role rendered
ansible.builtin.slurp:
src: "{{ matrix_alertmanager_receiver_config_path }}/config.yml"
register: matrix_alertmanager_receiver_config_file
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
- matrix_alertmanager_receiver_config_matrix_user_id in matrix_alertmanager_receiver_config_rendered
- matrix_alertmanager_receiver_config_matrix_access_token in matrix_alertmanager_receiver_config_rendered
- "'molecule-room' in matrix_alertmanager_receiver_config_rendered"
fail_msg: "The rendered configuration does not carry the scenario's Matrix settings"
success_msg: "The rendered configuration carries the scenario's Matrix settings"
vars:
matrix_alertmanager_receiver_config_rendered: "{{ matrix_alertmanager_receiver_config_file.content | b64decode }}"
- name: Assert the running container is the image the role pins
ansible.builtin.command:
argv:
- docker
- container
- inspect
- matrix-alertmanager-receiver
- --format
- "{{ '{{' }} .Config.Image {{ '}}' }}"
register: matrix_alertmanager_receiver_image
changed_when: false
- name: Assert the image carries the version defaults/main.yml pins
ansible.builtin.assert:
that:
- matrix_alertmanager_receiver_role_defaults.matrix_alertmanager_receiver_version in matrix_alertmanager_receiver_image.stdout
fail_msg: >-
The running container is {{ matrix_alertmanager_receiver_image.stdout }},
which does not carry the pinned version {{ matrix_alertmanager_receiver_role_defaults.matrix_alertmanager_receiver_version }}
success_msg: "The running container is the version defaults/main.yml pins"
- name: Read the labels the role rendered
ansible.builtin.slurp:
src: "{{ matrix_alertmanager_receiver_base_path }}/labels"
register: matrix_alertmanager_receiver_labels
- name: Assert no Traefik labels are emitted while Traefik support is disabled
ansible.builtin.assert:
that:
- "'traefik.' not in (matrix_alertmanager_receiver_labels.content | b64decode)"
fail_msg: >-
Traefik labels were emitted even though
matrix_alertmanager_receiver_container_labels_traefik_enabled is false
success_msg: "No Traefik labels are emitted while Traefik support is disabled"