Просмотр исходного кода

Test matrix-bot-meowlnir with Molecule

pull/5555/merge
Slavi Pantaleev 1 день назад
Родитель
Сommit
17a0094813
6 измененных файлов: 790 добавлений и 0 удалений
  1. +2
    -0
      .github/renovate.json
  2. +37
    -0
      roles/custom/matrix-bot-meowlnir/molecule/default/converge.yml
  3. +155
    -0
      roles/custom/matrix-bot-meowlnir/molecule/default/molecule.yml
  4. +91
    -0
      roles/custom/matrix-bot-meowlnir/molecule/default/prepare.yml
  5. +1
    -0
      roles/custom/matrix-bot-meowlnir/molecule/default/requirements.yml
  6. +504
    -0
      roles/custom/matrix-bot-meowlnir/molecule/default/verify.yml

+ 2
- 0
.github/renovate.json Просмотреть файл

@@ -127,6 +127,7 @@
"roles/custom/matrix-bot-draupnir/defaults/main.yml", "roles/custom/matrix-bot-draupnir/defaults/main.yml",
"roles/custom/matrix-bot-maubot/defaults/main.yml", "roles/custom/matrix-bot-maubot/defaults/main.yml",
"roles/custom/matrix-bot-matrix-reminder-bot/defaults/main.yml", "roles/custom/matrix-bot-matrix-reminder-bot/defaults/main.yml",
"roles/custom/matrix-bot-meowlnir/defaults/main.yml",
"roles/custom/matrix-bridge-heisenbridge/defaults/main.yml", "roles/custom/matrix-bridge-heisenbridge/defaults/main.yml",
"roles/custom/matrix-bridge-hookshot/defaults/main.yml", "roles/custom/matrix-bridge-hookshot/defaults/main.yml",
"roles/custom/matrix-bridge-mautrix-discord/defaults/main.yml", "roles/custom/matrix-bridge-mautrix-discord/defaults/main.yml",
@@ -153,6 +154,7 @@
"description": "Automerge minor bumps of explicitly approved components with a weight-bearing Molecule scenario (via branch push - no PR). This list is intentionally narrower than the patch rule: the scenario must exercise enough real behavior to be a useful compatibility gate across a minor release. A failure surfaces as a PR instead. Keep every entry backed by a scenario and the patch rule - bin/check-molecule-automerge-list.py (minor rule) enforces that.", "description": "Automerge minor bumps of explicitly approved components with a weight-bearing Molecule scenario (via branch push - no PR). This list is intentionally narrower than the patch rule: the scenario must exercise enough real behavior to be a useful compatibility gate across a minor release. A failure surfaces as a PR instead. Keep every entry backed by a scenario and the patch rule - bin/check-molecule-automerge-list.py (minor rule) enforces that.",
"matchFileNames": [ "matchFileNames": [
"roles/custom/matrix-authentication-service/defaults/main.yml", "roles/custom/matrix-authentication-service/defaults/main.yml",
"roles/custom/matrix-bot-meowlnir/defaults/main.yml",
"roles/custom/matrix-element-admin/defaults/main.yml", "roles/custom/matrix-element-admin/defaults/main.yml",
"roles/custom/matrix-static-files/defaults/main.yml", "roles/custom/matrix-static-files/defaults/main.yml",
"roles/custom/matrix-synapse-auto-compressor/defaults/main.yml" "roles/custom/matrix-synapse-auto-compressor/defaults/main.yml"


+ 37
- 0
roles/custom/matrix-bot-meowlnir/molecule/default/converge.yml Просмотреть файл

@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
- name: Include roles for matrix-bot-meowlnir Molecule tests
hosts: all
become: true
vars_files:
- "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
gather_facts: true
tasks:
- name: Include roles for matrix-bot-meowlnir 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
- "custom/{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}"
loop_control:
loop_var: role_name

# Component roles install their unit; the complete playbook's service manager starts it.
- name: Ensure matrix-bot-meowlnir 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 matrix-bot-meowlnir systemd service is started
ansible.builtin.systemd_service:
name: matrix-bot-meowlnir.service
state: started

+ 155
- 0
roles/custom/matrix-bot-meowlnir/molecule/default/molecule.yml Просмотреть файл

@@ -0,0 +1,155 @@
# 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: matrix-bot-meowlnir-${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:
matrix_bot_meowlnir_container_network: matrix-bot-meowlnir-molecule

# verify.yml is a separate play where role defaults are out of scope. Pin only
# paths it reads; the component version comes from the role's defaults directly.
matrix_bot_meowlnir_base_path: /matrix/meowlnir
matrix_bot_meowlnir_config_path: /matrix/meowlnir/config
matrix_bot_meowlnir_data_path: /matrix/meowlnir/data

# A local stub is available for bounded Matrix API calls. The empty-roster
# startup used here currently makes no outbound homeserver request, so the
# weight-bearing appservice gate is the authenticated inbound transaction in
# verify.yml. No real Matrix or third-party account is involved.
matrix_bot_meowlnir_config_homeserver_address: http://matrix.molecule.local:8008
matrix_bot_meowlnir_config_homeserver_domain: molecule.local
matrix_bot_meowlnir_appservice_id: molecule-meowlnir
matrix_bot_meowlnir_appservice_sender_localpart: molecule_meowlnir_as
matrix_bot_meowlnir_user_prefix: molecule_meowlnir_bot_
matrix_bot_meowlnir_appservice_token: molecule_as_token_2cf519
matrix_bot_meowlnir_homeserver_token: molecule_hs_token_c20dc3

# Real Postgres is the normal playbook path. The non-default credentials make its
# migrated schema evidence that Meowlnir consumed the URI assembled by the role.
matrix_bot_meowlnir_database_engine: postgres
matrix_bot_meowlnir_database_hostname: matrix-postgres-molecule
matrix_bot_meowlnir_database_name: molecule_meowlnir
matrix_bot_meowlnir_database_username: molecule_meowlnir
matrix_bot_meowlnir_database_password: molecule_pg_password_da31bc
matrix_bot_meowlnir_database_sslmode: disable
matrix_bot_meowlnir_config_database_max_open_conns: 13
matrix_bot_meowlnir_config_database_max_idle_conns: 3
matrix_bot_meowlnir_config_database_max_conn_idle_time: 47s
matrix_bot_meowlnir_config_database_max_conn_lifetime: 11m

# These differ from both role and upstream defaults. The authenticated management
# API below proves that the running process accepted the listener and secret.
matrix_bot_meowlnir_config_meowlnir_port: 29439
matrix_bot_meowlnir_appservice_url: http://matrix-bot-meowlnir:29439
matrix_bot_meowlnir_config_meowlnir_management_secret: molecule_management_secret_a64528
matrix_bot_meowlnir_config_meowlnir_data_secret: molecule_data_secret_e8c7b4
matrix_bot_meowlnir_config_meowlnir_federation_auth: true
matrix_bot_meowlnir_config_meowlnir_dry_run: true
matrix_bot_meowlnir_config_meowlnir_untrusted: true
matrix_bot_meowlnir_config_meowlnir_report_room: "!molecule-reports:molecule.local"
matrix_bot_meowlnir_config_meowlnir_room_ban_room: "!molecule-bans:molecule.local"
matrix_bot_meowlnir_config_meowlnir_hacky_rule_filter_custom:
- '@trusted:molecule.local'
- trusted.invalid
matrix_bot_meowlnir_config_meowlnir_hacky_redact_patterns_custom:
- molecule-abuse-*
matrix_bot_meowlnir_config_meowlnir_admin_tokens:
'@molecule-admin:molecule.local': molecule_admin_token_7e6ce0

matrix_bot_meowlnir_config_meowlnir4all_admin_room: "!molecule-provisioning:molecule.local"
matrix_bot_meowlnir_config_meowlnir4all_displayname: Molecule Provisioned Meowlnir
matrix_bot_meowlnir_config_meowlnir4all_avatar_url: mxc://molecule.local/meowlnir-avatar
matrix_bot_meowlnir_config_meowlnir4all_room_name: Molecule Meowlnir Control
matrix_bot_meowlnir_config_meowlnir4all_default_watched_lists_custom:
- name: Molecule policy list
room_id: "!molecule-policy:molecule.local"
shortcode: molecule
auto_unban: false

matrix_bot_meowlnir_config_antispam_secret: molecule_antispam_secret_59d553
matrix_bot_meowlnir_config_antispam_filter_local_invites: true
matrix_bot_meowlnir_config_antispam_auto_reject_invites_token: molecule_reject_token_a9812f
matrix_bot_meowlnir_config_antispam_notify_management_room: true
matrix_bot_meowlnir_config_antispam_block_invites_to_custom:
- '@blocked:molecule.local'

# Both integrations render public routing labels. Their real handlers are not the
# readiness gate; the management API and database provide stronger process evidence.
matrix_bot_meowlnir_policy_server_enabled: true
matrix_bot_meowlnir_config_policy_server_always_redact: false
matrix_bot_meowlnir_config_policy_server_signing_key: >-
ed25519 policy_server MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY
matrix_bot_meowlnir_config_reporting_enabled: true

matrix_bot_meowlnir_config_encryption_enable: false
matrix_bot_meowlnir_config_encryption_pickle_key: molecule_pickle_key_6f370e
matrix_bot_meowlnir_config_logging_min_level: debug
matrix_bot_meowlnir_config_logging_writers:
- type: stdout
format: json

# No bot is declared: this avoids accepting provisioning behavior which is green only
# because a deliberately permissive homeserver stub papered over it.
matrix_bot_meowlnir_bots_custom: []
matrix_bot_meowlnir_bots_pruning_enabled: false

# Traefik is not deployed. Distinct non-default routes still prove the role's label
# contract both on disk and in Docker's accepted container labels.
matrix_bot_meowlnir_container_labels_traefik_enabled: true
matrix_bot_meowlnir_container_labels_traefik_docker_network: matrix-bot-meowlnir-molecule
matrix_bot_meowlnir_container_labels_reporting_traefik_hostname: reports.molecule.local
matrix_bot_meowlnir_container_labels_reporting_traefik_path_regexp: ^/molecule-report/(rooms|users)/[^/]+$$
matrix_bot_meowlnir_container_labels_reporting_traefik_priority: 731
matrix_bot_meowlnir_container_labels_reporting_traefik_entrypoints: web
matrix_bot_meowlnir_container_labels_reporting_traefik_tls: false
matrix_bot_meowlnir_container_labels_policy_server_traefik_hostname: federation.molecule.local
matrix_bot_meowlnir_container_labels_policy_server_traefik_path_prefix: /molecule-policy
matrix_bot_meowlnir_container_labels_policy_server_traefik_priority: 733
matrix_bot_meowlnir_container_labels_policy_server_traefik_entrypoints: web
matrix_bot_meowlnir_container_labels_policy_server_traefik_tls: false
matrix_bot_meowlnir_container_labels_traefik_labels_additional_labels: |
molecule.meowlnir.coverage=enabled

# Preserve the production-like no-host-port path; probes join its Docker network.
matrix_bot_meowlnir_container_http_host_bind_port: ''
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

+ 91
- 0
roles/custom/matrix-bot-meowlnir/molecule/default/prepare.yml Просмотреть файл

@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
- name: Prepare matrix-bot-meowlnir 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

- name: Ensure Docker is installed
ansible.builtin.include_role:
name: ansible-role-docker
vars:
docker_daemon_options:
storage-driver: fuse-overlayfs

# matrix-base creates this identity in the complete playbook. Role file tasks resolve
# owner/group by name, so the role-scoped scenario supplies that side effect.
- 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 and the stub must join this network before the role's converge creates it.
- name: Ensure the container network the role attaches to exists
ansible.builtin.command:
argv:
- docker
- network
- create
- "{{ matrix_bot_meowlnir_container_network }}"
register: matrix_bot_meowlnir_molecule_network
changed_when: matrix_bot_meowlnir_molecule_network.rc == 0
failed_when:
- matrix_bot_meowlnir_molecule_network.rc != 0
- "'already exists' not in matrix_bot_meowlnir_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_bot_meowlnir_container_network }}"
molecule_shared_postgres_database: "{{ matrix_bot_meowlnir_database_name }}"
molecule_shared_postgres_username: "{{ matrix_bot_meowlnir_database_username }}"
molecule_shared_postgres_password: "{{ matrix_bot_meowlnir_database_password }}"

# Keep a local Matrix API available for bounded diagnostics and future provisioning
# coverage. Empty-roster startup currently makes no outbound homeserver request; the
# verifier therefore does not claim this stub as readiness evidence.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"
vars:
molecule_shared_stub_network: "{{ matrix_bot_meowlnir_container_network }}"
molecule_shared_stub_server_name: molecule.local
molecule_shared_stub_user_id: "@{{ matrix_bot_meowlnir_appservice_sender_localpart }}:{{ matrix_bot_meowlnir_config_homeserver_domain }}"

+ 1
- 0
roles/custom/matrix-bot-meowlnir/molecule/default/requirements.yml Просмотреть файл

@@ -0,0 +1 @@
../../../../../molecule-shared/requirements.yml

+ 504
- 0
roles/custom/matrix-bot-meowlnir/molecule/default/verify.yml Просмотреть файл

@@ -0,0 +1,504 @@
# SPDX-FileCopyrightText: 2026 Slavi Pantaleev
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# Meowlnir's authenticated management API and Postgres schema are the readiness evidence.
# The homeserver is a deliberately limited local stub, so no claim is made about moderation.
- name: Verify matrix-bot-meowlnir
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"
vars:
matrix_bot_meowlnir_rendered_config: "{{ matrix_bot_meowlnir_config_file.content | b64decode | from_yaml }}"
matrix_bot_meowlnir_rendered_registration: "{{ matrix_bot_meowlnir_registration_file.content | b64decode | from_yaml }}"
matrix_bot_meowlnir_api_body: "{{ matrix_bot_meowlnir_api_http.stdout_lines[:-1] | join('\n') }}"
matrix_bot_meowlnir_api_response: "{{ matrix_bot_meowlnir_api_body | from_json }}"
matrix_bot_meowlnir_expected_database_uri: >-
postgresql://{{ matrix_bot_meowlnir_database_username }}:{{ matrix_bot_meowlnir_database_password }}@{{ matrix_bot_meowlnir_database_hostname }}:5432/{{ matrix_bot_meowlnir_database_name }}?sslmode={{ matrix_bot_meowlnir_database_sslmode }}
matrix_bot_meowlnir_labels_lines: "{{ (matrix_bot_meowlnir_labels_file.content | b64decode).splitlines() }}"
matrix_bot_meowlnir_runtime: "{{ (matrix_bot_meowlnir_container_inspect.stdout | from_json) | first }}"
matrix_bot_meowlnir_config_mounts: "{{ matrix_bot_meowlnir_runtime.Mounts | selectattr('Destination', 'equalto', '/data/config') | list }}"
matrix_bot_meowlnir_data_mounts: "{{ matrix_bot_meowlnir_runtime.Mounts | selectattr('Destination', 'equalto', '/data') | list }}"
gather_facts: false
tasks:
- 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_bot_meowlnir_role_defaults

- name: Wait for the matrix-bot-meowlnir service to become active
ansible.builtin.systemd_service:
name: matrix-bot-meowlnir.service
register: matrix_bot_meowlnir_service
until: matrix_bot_meowlnir_service.status.ActiveState == 'active'
retries: 30
delay: 5
failed_when: false

- name: Assert the service is active and has not restarted
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_service.status.ActiveState == 'active'
- matrix_bot_meowlnir_service.status.NRestarts is defined
- matrix_bot_meowlnir_service.status.NRestarts | int == 0
fail_msg: >-
matrix-bot-meowlnir.service is
{{ matrix_bot_meowlnir_service.status.ActiveState | default('unknown') }} after
{{ matrix_bot_meowlnir_service.status.NRestarts | default('?') }} restart(s)
success_msg: "matrix-bot-meowlnir.service is active and has not restarted"

- name: Wait for Meowlnir's authenticated management API
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bot_meowlnir_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --header
- 'Authorization: Bearer molecule_management_secret_a64528'
- --write-out
- "\nHTTP_STATUS=%{http_code}"
- http://matrix-bot-meowlnir:29439/_meowlnir/v1/bots
register: matrix_bot_meowlnir_api_http
changed_when: false
until: "'HTTP_STATUS=200' in matrix_bot_meowlnir_api_http.stdout"
retries: 24
delay: 5
failed_when: false

- name: Assert the management API authenticated the configured secret
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_api_http.rc == 0
- matrix_bot_meowlnir_api_http.stdout_lines[-1] == 'HTTP_STATUS=200'
- matrix_bot_meowlnir_api_response is mapping
- matrix_bot_meowlnir_api_response.bots is sequence
- matrix_bot_meowlnir_api_response.bots | length == 0
fail_msg: >-
Meowlnir's management API did not authenticate or return its empty bot roster
({{ matrix_bot_meowlnir_api_http.stdout | default('no output') }})
success_msg: "Meowlnir's live management API authenticated the configured secret"

- name: Ask the management API with a wrong secret
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bot_meowlnir_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --header
- 'Authorization: Bearer molecule_wrong_secret'
- --output
- /dev/null
- --write-out
- "HTTP_STATUS=%{http_code}"
- http://matrix-bot-meowlnir:29439/_meowlnir/v1/bots
register: matrix_bot_meowlnir_wrong_secret_http
changed_when: false
failed_when: false

- name: Assert the management API rejects a wrong secret
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_wrong_secret_http.rc == 0
- matrix_bot_meowlnir_wrong_secret_http.stdout in ['HTTP_STATUS=401', 'HTTP_STATUS=403']
fail_msg: "Meowlnir's management API accepted the wrong bearer secret"
success_msg: "Meowlnir's management API rejects a wrong bearer secret"

# This is the appservice half of the live homeserver handshake. A valid empty
# transaction can only reach the dispatcher if the process loaded its hs_token.
- name: Send a homeserver transaction to Meowlnir's appservice API
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bot_meowlnir_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --request
- PUT
- --header
- 'Authorization: Bearer molecule_hs_token_c20dc3'
- --header
- 'Content-Type: application/json'
- --data
- '{"events":[],"ephemeral":[],"to_device":[]}'
- --write-out
- "\nHTTP_STATUS=%{http_code}"
- http://matrix-bot-meowlnir:29439/_matrix/app/v1/transactions/molecule-transaction
register: matrix_bot_meowlnir_transaction_http
changed_when: false
failed_when: false

- name: Assert Meowlnir accepts a transaction authenticated with the configured homeserver token
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_transaction_http.rc == 0
- matrix_bot_meowlnir_transaction_http.stdout_lines[-1] == 'HTTP_STATUS=200'
- matrix_bot_meowlnir_transaction_http.stdout_lines[:-1] | join('\n') | from_json == {}
fail_msg: >-
Meowlnir rejected an empty appservice transaction carrying the configured hs_token
({{ matrix_bot_meowlnir_transaction_http.stdout | default('no output') }})
success_msg: "Meowlnir accepts transactions carrying the configured homeserver token"

- name: Send an appservice transaction with a wrong homeserver token
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bot_meowlnir_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --request
- PUT
- --header
- 'Authorization: Bearer molecule_wrong_hs_token'
- --header
- 'Content-Type: application/json'
- --data
- '{"events":[]}'
- --output
- /dev/null
- --write-out
- "HTTP_STATUS=%{http_code}"
- http://matrix-bot-meowlnir:29439/_matrix/app/v1/transactions/molecule-wrong-token
register: matrix_bot_meowlnir_wrong_transaction_http
changed_when: false
failed_when: false

- name: Assert Meowlnir rejects a transaction with the wrong homeserver token
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_wrong_transaction_http.rc == 0
- matrix_bot_meowlnir_wrong_transaction_http.stdout == 'HTTP_STATUS=401'
fail_msg: "Meowlnir accepted an appservice transaction carrying a wrong hs_token"
success_msg: "Meowlnir rejects transactions carrying a wrong homeserver token"

- name: Probe the role's default Meowlnir port
ansible.builtin.command:
argv:
- docker
- run
- --rm
- --network={{ matrix_bot_meowlnir_container_network }}
- "{{ molecule_shared_image_curl }}"
- --silent
- --show-error
- --max-time
- '3'
- --output
- /dev/null
- --write-out
- "HTTP_STATUS=%{http_code}"
- http://matrix-bot-meowlnir:29339/_meowlnir/v1/bots
register: matrix_bot_meowlnir_default_port_http
changed_when: false
failed_when: false

- name: Assert Meowlnir is not listening on the role's default port
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_default_port_http.rc != 0
- matrix_bot_meowlnir_default_port_http.stdout == 'HTTP_STATUS=000'
fail_msg: "Meowlnir still answers on 29339, so the configured port is not proven"
success_msg: "Meowlnir does not listen on the role's default port"

- name: Read the configuration file the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bot_meowlnir_config_path }}/config.yaml"
register: matrix_bot_meowlnir_config_file

- name: Assert the parsed homeserver and appservice configuration
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_rendered_config.homeserver.address == matrix_bot_meowlnir_config_homeserver_address
- matrix_bot_meowlnir_rendered_config.homeserver.domain == matrix_bot_meowlnir_config_homeserver_domain
- matrix_bot_meowlnir_rendered_config.meowlnir.id == matrix_bot_meowlnir_appservice_id
- matrix_bot_meowlnir_rendered_config.meowlnir.as_token == matrix_bot_meowlnir_appservice_token
- matrix_bot_meowlnir_rendered_config.meowlnir.hs_token == matrix_bot_meowlnir_homeserver_token
- matrix_bot_meowlnir_rendered_config.meowlnir.address == matrix_bot_meowlnir_appservice_url
- matrix_bot_meowlnir_rendered_config.meowlnir.hostname == '0.0.0.0'
- matrix_bot_meowlnir_rendered_config.meowlnir.port == matrix_bot_meowlnir_config_meowlnir_port
fail_msg: "The parsed configuration lost the scenario's Matrix/appservice wiring"
success_msg: "The parsed configuration carries the scenario's Matrix/appservice wiring"

- name: Assert the parsed Meowlnir behavior and secrets
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_rendered_config.meowlnir.management_secret == matrix_bot_meowlnir_config_meowlnir_management_secret
- matrix_bot_meowlnir_rendered_config.meowlnir.data_secret == matrix_bot_meowlnir_config_meowlnir_data_secret
- matrix_bot_meowlnir_rendered_config.meowlnir.federation_auth is sameas true
- matrix_bot_meowlnir_rendered_config.meowlnir.dry_run is sameas true
- matrix_bot_meowlnir_rendered_config.meowlnir.untrusted is sameas true
- matrix_bot_meowlnir_rendered_config.meowlnir.report_room == '!molecule-reports:molecule.local'
- matrix_bot_meowlnir_rendered_config.meowlnir.room_ban_room == '!molecule-bans:molecule.local'
- matrix_bot_meowlnir_rendered_config.meowlnir.hacky_rule_filter == ['@trusted:molecule.local', 'trusted.invalid']
- matrix_bot_meowlnir_rendered_config.meowlnir.hacky_redact_patterns == ['spam', 'molecule-abuse-*']
- matrix_bot_meowlnir_rendered_config.meowlnir.admin_tokens['@molecule-admin:molecule.local'] == 'molecule_admin_token_7e6ce0'
fail_msg: "The parsed configuration lost the non-default Meowlnir behavior"
success_msg: "The parsed configuration carries the non-default Meowlnir behavior"

- name: Assert the parsed provisioning, antispam and policy configuration
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_rendered_config.meowlnir4all.admin_room == '!molecule-provisioning:molecule.local'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.localpart_template == matrix_bot_meowlnir_user_prefix + ('{' + '{ uuidgen }' + '}')
- matrix_bot_meowlnir_rendered_config.meowlnir4all.displayname == 'Molecule Provisioned Meowlnir'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.avatar_url == 'mxc://molecule.local/meowlnir-avatar'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.room_name == 'Molecule Meowlnir Control'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.default_watched_lists | length == 1
- matrix_bot_meowlnir_rendered_config.meowlnir4all.default_watched_lists[0].name == 'Molecule policy list'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.default_watched_lists[0].room_id == '!molecule-policy:molecule.local'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.default_watched_lists[0].shortcode == 'molecule'
- matrix_bot_meowlnir_rendered_config.meowlnir4all.default_watched_lists[0].auto_unban is sameas false
- matrix_bot_meowlnir_rendered_config.antispam.secret == matrix_bot_meowlnir_config_antispam_secret
- matrix_bot_meowlnir_rendered_config.antispam.filter_local_invites is sameas true
- matrix_bot_meowlnir_rendered_config.antispam.auto_reject_invites_token == 'molecule_reject_token_a9812f'
- matrix_bot_meowlnir_rendered_config.antispam.notify_management_room is sameas true
- matrix_bot_meowlnir_rendered_config.antispam.block_invites_to == ['@blocked:molecule.local']
- matrix_bot_meowlnir_rendered_config.policy_server.always_redact is sameas false
- matrix_bot_meowlnir_rendered_config.policy_server.signing_key.startswith('ed25519 policy_server ')
fail_msg: "The parsed provisioning/antispam/policy blocks lost scenario values"
success_msg: "The parsed provisioning/antispam/policy blocks carry scenario values"

- name: Assert the parsed Postgres and logging configuration
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_rendered_config.database.type == 'postgres'
- matrix_bot_meowlnir_rendered_config.database.uri == matrix_bot_meowlnir_expected_database_uri
- matrix_bot_meowlnir_rendered_config.database.max_open_conns == 13
- matrix_bot_meowlnir_rendered_config.database.max_idle_conns == 3
- matrix_bot_meowlnir_rendered_config.database.max_conn_idle_time == '47s'
- matrix_bot_meowlnir_rendered_config.database.max_conn_lifetime == '11m'
- matrix_bot_meowlnir_rendered_config.synapse_db.type == 'postgres'
- matrix_bot_meowlnir_rendered_config.synapse_db.uri == ''
- matrix_bot_meowlnir_rendered_config.logging.min_level == 'debug'
- "matrix_bot_meowlnir_rendered_config.logging.writers == [{'type': 'stdout', 'format': 'json'}]"
fail_msg: "The parsed database/logging blocks lost scenario values"
success_msg: "The parsed database/logging blocks carry scenario values"

- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bot_meowlnir_config_path }}/registration.yaml"
register: matrix_bot_meowlnir_registration_file

- name: Assert the parsed appservice registration contract
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_rendered_registration.id == matrix_bot_meowlnir_appservice_id
- matrix_bot_meowlnir_rendered_registration.as_token == matrix_bot_meowlnir_appservice_token
- matrix_bot_meowlnir_rendered_registration.hs_token == matrix_bot_meowlnir_homeserver_token
- matrix_bot_meowlnir_rendered_registration.url == matrix_bot_meowlnir_appservice_url
- matrix_bot_meowlnir_rendered_registration.sender_localpart == matrix_bot_meowlnir_appservice_sender_localpart
- matrix_bot_meowlnir_rendered_registration.rate_limited is sameas false
- matrix_bot_meowlnir_rendered_registration.namespaces.users | length == 2
- matrix_bot_meowlnir_rendered_registration.namespaces.users[0].exclusive is sameas true
- matrix_bot_meowlnir_rendered_registration.namespaces.users[0].regex == '^@molecule_meowlnir_bot_[a-zA-Z0-9._=/+-]+:molecule\\.local$'
- matrix_bot_meowlnir_rendered_registration.namespaces.users[1].regex == '^@molecule_meowlnir_as:molecule\\.local$'
- matrix_bot_meowlnir_rendered_registration['de.sorunome.msc2409.push_ephemeral'] is not defined
fail_msg: "The appservice registration lost its tokens, address or namespaces"
success_msg: "The appservice registration carries its tokens, address and namespaces"

- name: List the tables Meowlnir created in Postgres
ansible.builtin.command:
argv:
- docker
- exec
- matrix-postgres-molecule
- psql
- --username={{ matrix_bot_meowlnir_database_username }}
- --dbname={{ matrix_bot_meowlnir_database_name }}
- --tuples-only
- --no-align
- --command=SELECT tablename FROM pg_tables WHERE schemaname = 'public'
register: matrix_bot_meowlnir_tables
changed_when: false

- name: Assert Meowlnir migrated its schema into the configured Postgres database
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_tables.rc == 0
- "'version' in matrix_bot_meowlnir_table_names"
- "'bot' in matrix_bot_meowlnir_table_names"
- "'management_room' in matrix_bot_meowlnir_table_names"
- "'mx_version' in matrix_bot_meowlnir_table_names"
- "'mx_room_state' in matrix_bot_meowlnir_table_names"
- "'policy_server_signature' in matrix_bot_meowlnir_table_names"
- matrix_bot_meowlnir_table_names | length == 10
fail_msg: >-
Meowlnir did not create a substantial schema in {{ matrix_bot_meowlnir_database_name }}
(found {{ matrix_bot_meowlnir_table_names | length }} table(s))
success_msg: "Meowlnir migrated its schema into the configured Postgres database"
vars:
matrix_bot_meowlnir_table_names: "{{ matrix_bot_meowlnir_tables.stdout_lines | select | list }}"

- name: Read Meowlnir's two database migration markers
ansible.builtin.command:
argv:
- docker
- exec
- matrix-postgres-molecule
- psql
- --username={{ matrix_bot_meowlnir_database_username }}
- --dbname={{ matrix_bot_meowlnir_database_name }}
- --tuples-only
- --no-align
- --command=SELECT 'main:' || version || ':' || compat FROM version UNION ALL SELECT 'matrix_state:' || version || ':' || compat FROM mx_version ORDER BY 1
register: matrix_bot_meowlnir_migration_versions
changed_when: false

- name: Assert both Meowlnir schema sections reached their expected versions
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_migration_versions.rc == 0
- matrix_bot_meowlnir_migration_versions.stdout_lines | select | list == ['main:3:1', 'matrix_state:11:3']
fail_msg: >-
Meowlnir's main and Matrix-state migrations did not reach versions 3 and 11
({{ matrix_bot_meowlnir_migration_versions.stdout_lines | select | list }})
success_msg: "Both Meowlnir database schema sections reached their expected versions"

- name: Read the labels the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bot_meowlnir_base_path }}/labels"
register: matrix_bot_meowlnir_labels_file

- name: Assert the labels carry the configured reporting and policy routes
ansible.builtin.assert:
that:
- "'traefik.enable=true' in matrix_bot_meowlnir_labels_lines"
- "'traefik.docker.network=matrix-bot-meowlnir-molecule' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.services.matrix-bot-meowlnir.loadbalancer.server.port=29439' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-reporting.rule=Host(`reports.molecule.local`) && PathRegexp(`^/molecule-report/(rooms|users)/[^/]+$`)' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-reporting.priority=731' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-reporting.entrypoints=web' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-reporting.tls=false' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-policy-server.rule=Host(`federation.molecule.local`) && PathPrefix(`/molecule-policy`)' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-policy-server.priority=733' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-policy-server.entrypoints=web' in matrix_bot_meowlnir_labels_lines"
- "'traefik.http.routers.matrix-bot-meowlnir-policy-server.tls=false' in matrix_bot_meowlnir_labels_lines"
- "'molecule.meowlnir.coverage=enabled' in matrix_bot_meowlnir_labels_lines"
fail_msg: "The role's labels lost the non-default public routing contract"
success_msg: "The role's labels carry the non-default public routing contract"

- name: Inspect the running Meowlnir container
ansible.builtin.command:
argv:
- docker
- container
- inspect
- matrix-bot-meowlnir
register: matrix_bot_meowlnir_container_inspect
changed_when: false

- name: Assert the running container uses the exact image pinned by the role
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.Config.Image == matrix_bot_meowlnir_expected_image
fail_msg: "The running Meowlnir container does not use the role's exact image pin"
success_msg: "The running container uses the exact image pinned by the role"
vars:
matrix_bot_meowlnir_expected_image: >-
{{ matrix_bot_meowlnir_role_defaults.matrix_bot_meowlnir_container_image_registry_prefix_upstream_default }}{{ matrix_bot_meowlnir_role_defaults.matrix_bot_meowlnir_container_image_registry_namespace_identifier }}:{{ matrix_bot_meowlnir_role_defaults.matrix_bot_meowlnir_version }}

- name: Assert the running container uses the playbook's matrix identity
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.Config.User.split(':')[0] == matrix_user_uid | string
- matrix_bot_meowlnir_runtime.Config.User.split(':')[1] == matrix_user_gid | string
fail_msg: >-
Meowlnir runs as {{ matrix_bot_meowlnir_runtime.Config.User }} instead of
{{ matrix_user_uid }}:{{ matrix_user_gid }}
success_msg: "The running container uses the playbook's matrix UID and GID"

- name: Assert the running container has the role's exact command
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.Config.Cmd == ['/usr/bin/meowlnir', '--config=/data/config/config.yaml', '--no-update']
fail_msg: "The running Meowlnir container command differs from the role contract"
success_msg: "The running container has the role's exact command"

- name: Assert the container root filesystem is read-only
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.HostConfig.ReadonlyRootfs is sameas true
fail_msg: "The Meowlnir container root filesystem is writable"
success_msg: "The Meowlnir container root filesystem is read-only"

- name: Assert all Linux capabilities are dropped
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.HostConfig.CapDrop == ['ALL']
fail_msg: "The Meowlnir container does not drop all Linux capabilities"
success_msg: "The Meowlnir container drops all Linux capabilities"

- name: Assert the configuration bind mount is read-only
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_config_mounts | length == 1
- matrix_bot_meowlnir_config_mounts[0].RW is sameas false
- matrix_bot_meowlnir_config_mounts[0].Source == matrix_bot_meowlnir_config_path
fail_msg: "The Meowlnir configuration bind mount is missing, misplaced or writable"
success_msg: "The configuration bind mount is present and read-only"

- name: Assert the data bind mount is writable
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_data_mounts | length == 1
- matrix_bot_meowlnir_data_mounts[0].RW is sameas true
- matrix_bot_meowlnir_data_mounts[0].Source == matrix_bot_meowlnir_data_path
fail_msg: "The Meowlnir data bind mount is missing, misplaced or read-only"
success_msg: "The data bind mount is present and writable"

- name: Assert the container is attached only to its dedicated network
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.NetworkSettings.Networks is mapping
- matrix_bot_meowlnir_runtime.NetworkSettings.Networks | length == 1
- matrix_bot_meowlnir_container_network in matrix_bot_meowlnir_runtime.NetworkSettings.Networks
fail_msg: >-
Meowlnir has unexpected networks:
{{ matrix_bot_meowlnir_runtime.NetworkSettings.Networks.keys() | list }}
success_msg: "The container is attached only to its dedicated network"

- name: Ask Docker for Meowlnir's published ports
ansible.builtin.command:
argv:
- docker
- container
- port
- matrix-bot-meowlnir
register: matrix_bot_meowlnir_published_ports
changed_when: false
failed_when: false

- name: Assert the role did not publish a host port
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.HostConfig.PortBindings | default({}, true) | length == 0
- matrix_bot_meowlnir_published_ports.rc == 0
- matrix_bot_meowlnir_published_ports.stdout | trim | length == 0
fail_msg: >-
Meowlnir unexpectedly publishes a host port:
{{ matrix_bot_meowlnir_published_ports.stdout | default('unknown') }}
success_msg: "The role leaves Meowlnir's HTTP port unpublished"

- name: Assert Docker accepted the role's custom label
ansible.builtin.assert:
that:
- matrix_bot_meowlnir_runtime.Config.Labels is mapping
- matrix_bot_meowlnir_runtime.Config.Labels['molecule.meowlnir.coverage'] == 'enabled'
fail_msg: "Docker did not attach the custom label from the role's label file"
success_msg: "Docker accepted the role's custom label"

Загрузка…
Отмена
Сохранить