Parcourir la source

Reword the Molecule scenario comments

They were hard-wrapped at 80 characters, broke mid-parenthesis, and spent lines
restating what the code below them does.

Rewrapped at natural boundaries instead, with the narration dropped and only the
reasons, gotchas and surprises kept. Section dividers stay - they delineate long
plays rather than narrate them.

Comments only; no scenario behaviour changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
pull/5575/head
Slavi Pantaleev il y a 12 heures
Parent
révision
c447e1528b
32 fichiers modifiés avec 460 ajouts et 643 suppressions
  1. +27
    -33
      molecule-shared/homeserver-stub.py
  2. +18
    -28
      molecule-shared/playbook-context.yml
  3. +1
    -3
      molecule-shared/requirements.yml
  4. +11
    -14
      molecule-shared/tasks/homeserver-stub.yml
  5. +6
    -8
      molecule-shared/vars.yml
  6. +5
    -7
      roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml
  7. +8
    -11
      roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml
  8. +5
    -7
      roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml
  9. +13
    -17
      roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml
  10. +5
    -7
      roles/custom/matrix-bot-baibot/molecule/default/converge.yml
  11. +13
    -20
      roles/custom/matrix-bot-baibot/molecule/default/molecule.yml
  12. +6
    -9
      roles/custom/matrix-bot-baibot/molecule/default/prepare.yml
  13. +37
    -54
      roles/custom/matrix-bot-baibot/molecule/default/verify.yml
  14. +5
    -7
      roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml
  15. +14
    -20
      roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml
  16. +7
    -10
      roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml
  17. +34
    -48
      roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml
  18. +11
    -13
      roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml
  19. +4
    -5
      roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml
  20. +10
    -14
      roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml
  21. +25
    -35
      roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml
  22. +2
    -3
      roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml
  23. +42
    -52
      roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml
  24. +17
    -27
      roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml
  25. +4
    -6
      roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml
  26. +30
    -40
      roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml
  27. +21
    -36
      roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml
  28. +5
    -8
      roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml
  29. +42
    -57
      roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml
  30. +10
    -16
      roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml
  31. +4
    -5
      roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml
  32. +18
    -23
      roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml

+ 27
- 33
molecule-shared/homeserver-stub.py Voir le fichier

@@ -4,21 +4,18 @@

"""A stand-in homeserver for Molecule scenarios.

Most components in this playbook talk to a homeserver while starting up and
exit if it is unreachable, so a scenario cannot get them running without one.
Standing up a real Synapse for every role would dominate the run time and drag
in Postgres, and the scenarios are not testing Synapse - they are testing that
the role's configuration reaches the component and that it starts.

So this answers the handful of endpoints components touch during startup, with
the blandest plausible response in each case. It is deliberately permissive: an
unknown path returns `{}` with a 200 rather than a 404, because the goal is to
get the component past its startup checks, not to model the Matrix spec.

What it is NOT: an authentication check, a room state machine, or anything a
scenario should assert *about*. Assert on what the role rendered and on what the
component reports about itself. If a scenario starts needing this stub to behave
like a real homeserver, that scenario has outgrown what these tests are for.
Most components talk to a homeserver while starting up and exit if it is unreachable,
so a scenario cannot get them running without one. A real Synapse for every role would
dominate the run time and drag in Postgres, and the scenarios are not testing Synapse.

This answers the handful of endpoints components touch during startup, with the blandest
plausible response in each case. Deliberately permissive: an unknown path returns `{}` with
a 200 rather than a 404, because the goal is to get the component past its startup checks.

What it is NOT: an authentication check, a room state machine, or anything a scenario should
assert *about*. Assert on what the role rendered and what the component reports about itself.
A scenario that needs this stub to behave like a real homeserver has outgrown what these
tests are for.
"""

import json
@@ -32,18 +29,17 @@ from urllib.parse import parse_qs, urlparse
SERVER_NAME = os.environ.get("STUB_SERVER_NAME", "molecule.local")
PORT = int(os.environ.get("STUB_PORT", "8008"))

# Rooms reported as already joined. Components that resolve a room mapping at
# startup (matrix-alertmanager-receiver, for one) fail if the rooms they were
# configured with are missing, so a scenario passes its own room IDs in.
# Rooms reported as already joined. Components that resolve a room mapping at startup
# (matrix-alertmanager-receiver, for one) fail if the rooms they were configured with
# are missing, so a scenario passes its own room IDs in.
JOINED_ROOMS = [r for r in os.environ.get("STUB_JOINED_ROOMS", "").split(",") if r]

USER_ID = os.environ.get("STUB_USER_ID", f"@stub:{SERVER_NAME}")

# Longest a /sync call is held open. Long-polling clients (anything on
# matrix-sdk: baibot and the other bots) ask for a 30s timeout and immediately
# ask again when the call returns, so answering instantly would spin them into a
# hot loop that eats the test machine. Honouring the requested timeout, capped
# here, keeps an idle bot idle.
# Longest a /sync call is held open. Long-polling clients ask for a 30s timeout and
# immediately ask again when the call returns, so answering instantly spins them into a hot
# loop that eats the test machine. Honouring the requested timeout, capped here, keeps an
# idle bot idle.
SYNC_MAX_HOLD_SECONDS = 30


@@ -109,12 +105,10 @@ class Handler(BaseHTTPRequestHandler):
if path.endswith("/capabilities"):
return {"capabilities": {}}

# Bots that authenticate with a username and password rather than as an
# appservice with a token log in here. Matched loosely on purpose:
# clients differ on the API version prefix (matrix-nio has shipped both
# /_matrix/client/r0/login and /_matrix/client/v3/login over time), and a
# login that falls through to the catch-all `{}` below looks to the
# client like bad credentials.
# Where bots authenticating with a username and password log in, rather than as an
# appservice with a token. Matched loosely on purpose, because clients differ on the
# API version prefix, and a login falling through to the catch-all `{}` below looks
# to the client like bad credentials.
if path.endswith("/login"):
return {
"user_id": USER_ID,
@@ -155,8 +149,8 @@ class Handler(BaseHTTPRequestHandler):
if path.endswith("/health") or path.endswith("/_matrix/federation/v1/version"):
return {"server": {"name": "molecule-stub", "version": "0"}}

# Anything unrecognised: an empty object, so a component doing a startup
# probe of an endpoint not listed here still gets past it.
# Anything unrecognised: an empty object, so a component probing an endpoint
# not listed here still gets past it.
return {}

def do_GET(self):
@@ -175,8 +169,8 @@ class Handler(BaseHTTPRequestHandler):
self._send({})

def log_message(self, fmt, *args):
# Quiet by default; STUB_VERBOSE=1 when a scenario will not start and you
# need to see what the component is actually asking for.
# Quiet by default. STUB_VERBOSE=1 when a scenario will not start and you need
# to see what the component is actually asking for.
if os.environ.get("STUB_VERBOSE"):
sys.stderr.write("stub: " + (fmt % args) + "\n")



+ 18
- 28
molecule-shared/playbook-context.yml Voir le fichier

@@ -3,21 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# The variables a role here reads from its surroundings rather than from its own
# defaults. In a real run `matrix-base` and `group_vars/matrix_servers` provide
# them; in a scenario they have to come from somewhere, and including
# `matrix-base` itself does far more than a role scenario needs.
# The variables a role reads from its surroundings rather than from its own defaults.
# In a real run `matrix-base` and `group_vars/matrix_servers` provide them.
#
# Include from a scenario's prepare.yml, converge.yml and verify.yml:
#
# vars_files:
# - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
#
# A scenario can override any of these in its own group_vars - that is the point
# of testing a role with values it would not have chosen for itself.
# Gotcha: `vars_files` outranks inventory `group_vars`, so a scenario cannot override these there.
#
# Keep this to variables that come from OUTSIDE the role under test. Anything the
# role defines belongs in the scenario, not here.
# Keep this to variables that come from OUTSIDE the role under test.
# Anything the role defines itself belongs in the scenario.

# --- Identity and paths (matrix-base) --------------------------------------

@@ -27,32 +24,26 @@ matrix_domain: molecule.local
matrix_user_name: matrix
matrix_group_name: matrix

# Deliberately not 1000: the base images already have a user there, so a distinct
# id is what proves a role used the one it was given rather than coinciding with
# the image's own.
# Deliberately not 1000: the base images already have a user there, so a distinct id
# is what proves a role used the one it was given rather than coinciding with the image's own.
matrix_user_uid: 1234
matrix_user_gid: 1234

# Empty in the playbook's own defaults too. Components that would invite an
# administrator into a room skip doing so when it is empty, which is what a
# scenario wants.
# Empty in the playbook's own defaults too. Components that would invite an administrator
# into a room skip doing so when it is empty.
matrix_admin: ''

# --- Host commands (matrix-base) -------------------------------------------
#
# Some roles shell out to a host binary through this indirection instead of
# naming it directly (matrix-bridge-hookshot and matrix-bridge-appservice-irc
# both generate a key with it). matrix-base's defaults are what supplies the
# value in a real run; those roles install the binary themselves, by including
# matrix-base's `ensure_openssl_installed` tasks.

# Some roles shell out through this indirection instead of naming the binary directly
# (matrix-bridge-hookshot and matrix-bridge-appservice-irc both generate a key with it).
# They install it themselves by including matrix-base's `ensure_openssl_installed` tasks.
matrix_host_command_openssl: "/usr/bin/env openssl"

# --- Bridge-wide switches (matrix-base) ------------------------------------
#
# Every bridge role reads these, so they live here rather than in each bridge's
# scenario. The values match the playbook's own defaults: encryption off, no
# relay, no MSC4190. A bridge scenario that wants to prove one of these reaches

# Every bridge role reads these, so they live here rather than in each bridge's scenario.
# The values match the playbook's own defaults. A scenario proving one of these reaches
# the rendered configuration should override it in its own group_vars.

matrix_bridges_encryption_enabled: false
@@ -66,10 +57,9 @@ matrix_bridges_exposure_hostname: molecule.local
matrix_bridges_exposure_path_prefix: /bridges

# --- Public hostnames (matrix-base) ----------------------------------------
#
# 18 of the roles here read one of these. Rendered against the scenario's
# matrix_domain rather than left as Jinja, so a scenario can read them in
# verify.yml without the role's defaults being in scope.

# 18 of the roles here read one of these. Rendered against the scenario's matrix_domain
# rather than left as Jinja, so verify.yml can read them without the role's defaults in scope.

matrix_server_fqn_matrix: matrix.molecule.local
matrix_server_fqn_matrix_federation: matrix.molecule.local


+ 1
- 3
molecule-shared/requirements.yml Voir le fichier

@@ -1,7 +1,5 @@
---
# Shared by every role scenario under roles/custom/*/molecule/, referenced from
# each scenario's molecule.yml. Kept in one place so the pins cannot drift
# apart across roles.
# Shared by every role scenario, so the pins cannot drift apart across roles.
roles:
- name: ansible-role-docker
src: https://github.com/geerlingguy/ansible-role-docker


+ 11
- 14
molecule-shared/tasks/homeserver-stub.yml Voir le fichier

@@ -3,8 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# Stands up a stand-in homeserver on a container network, for scenarios whose
# component contacts a homeserver while starting up.
# Stands up a stand-in homeserver on a container network, for scenarios whose component
# contacts a homeserver while starting up.
#
# Include from a scenario's prepare.yml:
#
@@ -17,8 +17,7 @@
#
# The component should then be pointed at http://matrix.molecule.local:8008.
#
# See molecule-shared/homeserver-stub.py for what it answers and, more
# importantly, for what it is not.
# See molecule-shared/homeserver-stub.py for what it answers and, more importantly, what it is not.

- name: Ensure the homeserver stub script is present
ansible.builtin.copy:
@@ -37,8 +36,8 @@
changed_when: molecule_shared_stub_removal.rc == 0
failed_when: false

# The alias is what the component resolves, so its configuration can name a
# hostname rather than a container name.
# The alias is what the component resolves, so its configuration can name a hostname
# rather than a container name.
- name: Ensure the homeserver stub is running
ansible.builtin.command:
argv:
@@ -50,15 +49,13 @@
- --network-alias={{ molecule_shared_stub_hostname | default('matrix.molecule.local') }}
- --env=STUB_SERVER_NAME={{ molecule_shared_stub_server_name | default('molecule.local') }}
- --env=STUB_JOINED_ROOMS={{ (molecule_shared_stub_joined_rooms | default([])) | join(',') }}
# Appservices call /whoami on startup and refuse to run if the id
# returned is not the bot user they were configured as, so a scenario
# bridging anything has to tell the stub who it should claim to be.
# Appservices call /whoami on startup and refuse to run if the id returned is not
# the bot user they were configured as, so a scenario bridging anything has to tell
# the stub who it should claim to be.
- --env=STUB_USER_ID={{ molecule_shared_stub_user_id | default('@stub:' + (molecule_shared_stub_server_name | default('molecule.local'))) }}
# Off by default. Set molecule_shared_stub_verbose to "1" to have the stub
# log every request it is asked for, which is both how you find out why a
# component will not start and - for a component that exposes no port of
# its own - the only place a scenario can observe it acting on what the
# role configured.
# Set molecule_shared_stub_verbose to "1" to log every request the stub is asked for.
# How you find out why a component will not start, and for a component with no port of
# its own, the only place to observe it acting on what the role configured.
- --env=STUB_VERBOSE={{ molecule_shared_stub_verbose | default('') }}
- --volume=/root/molecule-homeserver-stub.py:/stub.py:ro
- "{{ molecule_shared_image_python }}"


+ 6
- 8
molecule-shared/vars.yml Voir le fichier

@@ -1,13 +1,11 @@
---
# Helper container images the scenarios use for probing. They live here rather
# than inline in each verify.yml so that there is one pin per image instead of
# one per role, and so Renovate can see them (see the customManager in
# .github/renovate.json).
# Helper container images the scenarios use for probing. Here rather than inline in each
# verify.yml, so there is one pin per image instead of one per role, and so Renovate can see
# them. See the customManager in .github/renovate.json.

# Used to reach a role's container over its own container network. A helper is
# needed because the role publishes no host port - exactly as in a real
# deployment - and publishing one for the test would collide between scenarios
# running in parallel.
# Used to reach a role's container over its own container network, because the role publishes
# no host port - exactly as in a real deployment. Publishing one for the test would collide
# between scenarios running in parallel.
# renovate: datasource=docker depName=docker.io/curlimages/curl
molecule_shared_image_curl: "docker.io/curlimages/curl:8.11.1"



+ 5
- 7
roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml Voir le fichier

@@ -3,11 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# The devture base roles carry the variables this role reads
# (`devture_systemd_docker_base_*`, `devture_playbook_help_*`), the same way
# they do when the playbook runs. `matrix-base` is deliberately NOT included:
# it does far more than this role needs, and the two variables it would supply
# are set directly in molecule.yml instead.
# The devture base roles carry the variables this role reads, the same way they do when
# the playbook runs. `matrix-base` is deliberately NOT included: it does far more than this
# role needs, and the two variables it would supply are set directly in molecule.yml.
- name: Include roles for matrix-alertmanager-receiver Molecule tests
hosts: all
become: true
@@ -26,8 +24,8 @@
loop_control:
loop_var: role_name

# The role installs the unit but does not start it - in the playbook that is
# `systemd_service_manager`'s job - so the scenario starts it here.
# The role installs the unit but does not start it; in the playbook that is
# `systemd_service_manager`'s job.
- name: Ensure matrix-alertmanager-receiver is started
hosts: all
become: true


+ 8
- 11
roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml Voir le fichier

@@ -31,27 +31,24 @@ provisioner:
matrix_alertmanager_receiver_path_prefix: /
matrix_alertmanager_receiver_container_network: matrix-alertmanager-receiver-molecule

# verify.yml runs as its own play, where the role's defaults are out
# of scope, so the paths it reads are pinned here as literals. They
# match what the role derives from matrix_base_data_path above.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_alertmanager_receiver_base_path: /matrix/alertmanager-receiver
matrix_alertmanager_receiver_config_path: /matrix/alertmanager-receiver/config

# Traefik is not deployed in this scenario, so the labels the role would
# render for it are switched off and their absence is asserted instead.
# Traefik is not deployed here, so the labels the role would render for it are
# switched off and their absence is asserted instead.
matrix_alertmanager_receiver_container_labels_traefik_enabled: false

# Deliberately different from the role's own defaults (port 12345,
# metrics disabled, alerts under /alerts), so that `verify.yml` can tell
# what the role rendered apart from what the application would have done
# on its own.
# Different from the role's own defaults, so verify.yml can tell what the role
# rendered apart from what the application would have done on its own.
matrix_alertmanager_receiver_config_http_port: 12399
matrix_alertmanager_receiver_config_http_metrics_enabled: true
matrix_alertmanager_receiver_config_http_metrics_path: /molecule-metrics
matrix_alertmanager_receiver_config_http_alerts_path_prefix: /molecule-alerts

# The homeserver IS reached at startup - the service fetches its joined
# rooms and exits 1 if that fails - so prepare.yml stands up a stub for it
# The homeserver IS reached at startup: the service fetches its joined rooms and
# exits 1 if that fails, so prepare.yml stands up a stub for it.
matrix_alertmanager_receiver_config_matrix_homeserver_url: http://matrix.molecule.local:8008
matrix_alertmanager_receiver_config_matrix_user_id: "@alertmanager:molecule.local"
matrix_alertmanager_receiver_config_matrix_access_token: molecule_access_token_4f2a91


+ 5
- 7
roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name, and Ansible resolves those
# through the passwd database - so they have to exist before it runs. In a
# real deployment `matrix-base` creates them.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -70,10 +69,9 @@
- matrix_alertmanager_receiver_molecule_network.rc != 0
- "'already exists' not in matrix_alertmanager_receiver_molecule_network.stderr"

# matrix-alertmanager-receiver contacts the homeserver while starting up -
# it fetches /_matrix/client/v3/joined_rooms to resolve its room mapping and
# exits 1 if that fails - so a homeserver has to exist for it to come up at
# all. The shared stub is enough; see molecule-shared/homeserver-stub.py.
# The service fetches /_matrix/client/v3/joined_rooms to resolve its room mapping and
# exits 1 if that fails, so a homeserver has to exist for it to come up at all.
# The shared stub is enough. See molecule-shared/homeserver-stub.py.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 13
- 17
roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml Voir le fichier

@@ -12,10 +12,9 @@
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.
# Read from the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what defaults/main.yml 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"
@@ -30,10 +29,9 @@
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.
# `Restart=always` means a crash-looping container still reports `active`, so the restart
# counter is checked too. Asserted `is defined` because `| int` turns a missing property
# into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -47,9 +45,9 @@
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.
# Probed from inside the container network rather than the host, because the role
# publishes no host port - exactly as in a real deployment, where Traefik reaches it
# over the network.
- name: Wait for matrix-alertmanager-receiver to answer on the port the role configured
ansible.builtin.command:
argv:
@@ -70,9 +68,8 @@
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.
# Port and metrics path are both non-default here, so a 200 is only reachable if what
# the role rendered is what the process is running on.
- name: Assert the configured port and metrics path reached the process
ansible.builtin.assert:
that:
@@ -93,9 +90,8 @@
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.
# 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.
- name: Ask for the role's default metrics path, which this scenario moved away from
ansible.builtin.command:
argv:


+ 5
- 7
roles/custom/matrix-bot-baibot/molecule/default/converge.yml Voir le fichier

@@ -3,11 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# The devture base roles carry the variables this role reads
# (`devture_systemd_docker_base_*`, `devture_playbook_help_*`), the same way
# they do when the playbook runs. `matrix-base` is deliberately NOT included:
# it does far more than this role needs, and what it would supply comes from
# molecule-shared/playbook-context.yml instead.
# The devture base roles carry the variables this role reads, the same way they do when
# the playbook runs. `matrix-base` is deliberately NOT included: it does far more than this
# role needs, and what it would supply comes from molecule-shared/playbook-context.yml.
- name: Include roles for matrix-bot-baibot Molecule tests
hosts: all
become: true
@@ -26,8 +24,8 @@
loop_control:
loop_var: role_name

# The role installs the unit but does not start it - in the playbook that is
# `systemd_service_manager`'s job - so the scenario starts it here.
# The role installs the unit but does not start it; in the playbook that is
# `systemd_service_manager`'s job.
- name: Ensure matrix-bot-baibot is started
hosts: all
become: true


+ 13
- 20
roles/custom/matrix-bot-baibot/molecule/default/molecule.yml Voir le fichier

@@ -29,22 +29,18 @@ provisioner:
all:
matrix_bot_baibot_container_network: matrix-bot-baibot-molecule

# verify.yml runs as its own play, where the role's defaults are out
# of scope, so the paths it reads are pinned here as literals. They
# match what the role derives from matrix_base_data_path.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_bot_baibot_base_path: /matrix/baibot
matrix_bot_baibot_config_path: /matrix/baibot/config
matrix_bot_baibot_data_path: /matrix/baibot/data

# baibot is a plain Matrix client, not an appservice: it logs in with a
# password and then syncs, so a homeserver has to answer for it to get
# anywhere. prepare.yml stands up the shared stub for that.
# baibot is a plain Matrix client, not an appservice: it logs in with a password
# and then syncs, so a homeserver has to answer for it to get anywhere.
matrix_bot_baibot_config_homeserver_url: http://matrix.molecule.local:8008

# Deliberately different from the role's defaults (localpart `baibot`,
# name `baibot`, prefix `!bai`, self-introduction on) AND from baibot's
# own built-in defaults, so that a passing assertion cannot be explained
# by "it would have happened anyway".
# Deliberately different from the role's defaults AND from baibot's own, so a passing
# assertion cannot be explained by "it would have happened anyway".
matrix_bot_baibot_config_user_mxid_localpart: molecule-baibot
matrix_bot_baibot_config_user_name: Molecule baibot
matrix_bot_baibot_config_user_password: molecule_baibot_password_5b7c14
@@ -53,18 +49,15 @@ provisioner:
matrix_bot_baibot_config_access_admin_patterns:
- "@molecule-admin:molecule.local"

# `debug` rather than the role's `info`, so the journal carries what the
# bot loaded. verify.yml reads it.
# `debug` rather than the role's `info`, so the journal carries what the bot loaded.
matrix_bot_baibot_config_logging_level_baibot: debug

# baibot talks to AI providers, and a scenario must not need a provider
# account. It does not have to: providers are contacted only when a
# message asks an agent to do something, never at startup. So a static
# agent is defined with a placeholder key and a base URL that resolves
# nowhere. Nothing is ever called, and the agent still has to survive
# baibot's startup parsing of `agents.static_definitions` - which is
# what proves the role's provider templating produced something the bot
# accepts.
# A scenario must not need an AI provider account, and does not have to: providers are
# contacted only when a message asks an agent to do something, never at startup.
# So the agent below carries a placeholder key and a base URL that resolves nowhere.
# Nothing is ever called, yet the definition still has to survive baibot's startup
# parsing - which is what proves the role's provider templating produced something
# the bot accepts.
matrix_bot_baibot_config_agents_static_definitions_anthropic_enabled: true
matrix_bot_baibot_config_agents_static_definitions_anthropic_id: molecule-anthropic
matrix_bot_baibot_config_agents_static_definitions_anthropic_config_base_url: http://molecule-no-such-provider.invalid/v1


+ 6
- 9
roles/custom/matrix-bot-baibot/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name, and Ansible resolves those
# through the passwd database - so they have to exist before it runs. In a
# real deployment `matrix-base` creates them.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -70,13 +69,11 @@
- matrix_bot_baibot_molecule_network.rc != 0
- "'already exists' not in matrix_bot_baibot_molecule_network.stderr"

# baibot logs in and then syncs forever; with no homeserver answering it
# never gets past login and the unit crash-loops. The shared stub is enough
# - see molecule-shared/homeserver-stub.py for what it is not.
# baibot logs in and then syncs forever; with no homeserver answering it never gets past
# login. The shared stub is enough - see molecule-shared/homeserver-stub.py for what it is not.
#
# It is told to claim the bot's own MXID, because the bot resolves who it is
# from what the homeserver hands back at login, and everything it does
# afterwards (its profile, its own-message filtering) hangs off that.
# Told to claim the bot's own MXID, because the bot resolves who it is from what the
# homeserver hands back at login, and its profile and own-message filtering hang off that.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 37
- 54
roles/custom/matrix-bot-baibot/molecule/default/verify.yml Voir le fichier

@@ -12,10 +12,9 @@
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.
# Read from the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what defaults/main.yml 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"
@@ -30,10 +29,9 @@
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.
# `Restart=always` means a crash-looping container still reports `active`, so the restart
# counter is checked too. Asserted `is defined` because `| int` turns a missing property
# into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -47,17 +45,15 @@
automatic restart(s)
success_msg: "matrix-bot-baibot.service is active and has not restarted"

# baibot publishes no port of its own - it is a Matrix client, not a server -
# so what it says about itself has to come from its output. The unit runs
# `docker start --attach`, so `--log-driver=none` on the container does not
# stop the journal from carrying it.
# baibot is a Matrix client, not a server, so what it says about itself has to come from
# its output. The unit runs `docker start --attach`, so `--log-driver=none` does not stop
# the journal from carrying it.
#
# `Syncing..` is the line that matters, and it is what carries this scenario
# rather than the unit check above. baibot does not exit when its startup
# goes wrong: a profile step it cannot complete is retried forever with a
# growing delay, so the unit stays `active` with `NRestarts` at 0 while the
# bot never reaches its message loop. Point the avatar at a file that is not
# there and the assertion above still passes; this one does not.
# `Syncing..` is what carries this scenario, not the unit check above. baibot does not exit
# when startup goes wrong: a profile step it cannot complete is retried forever with a
# growing delay, so the unit stays `active` with `NRestarts` at 0 while the bot never
# reaches its message loop. Point the avatar at a missing file and the assertion above
# still passes; this one does not.
- name: Wait for baibot to reach its sync loop
ansible.builtin.shell:
cmd: >-
@@ -81,10 +77,8 @@
retrying profile setup
success_msg: "baibot got past startup and is syncing"

# `user.name` is the bot's display name. The scenario's value is neither the
# role's default (`baibot`) nor what the stub reports the account already has
# (`stub`), so the bot naming this as what it wants can only have come from
# the configuration the role rendered.
# The scenario's display name is neither the role's default nor what the stub reports the
# account already has, so the bot wanting it can only have come from what the role rendered.
- name: Assert the display name the role configured reached the process
ansible.builtin.assert:
that:
@@ -96,22 +90,19 @@
display name it wants, so `user.name` did not reach the process
success_msg: "baibot acts on the display name the role configured"

# The `logging` setting is one string carrying per-target levels
# (`warn,mxlink=info,baibot=debug`), so proving it arrived means proving that
# different targets ended up at different levels - a single global level
# would satisfy neither half of this.
# `logging` is one string carrying per-target levels, so proving it arrived means proving
# different targets ended up at different levels. A single global level satisfies neither half.
#
# First clause: baibot's own records appear at DEBUG, which the role's
# default of `info` would not produce.
# First clause: baibot's own records appear at DEBUG, which the role's default of `info`
# would not produce.
#
# Second clause is the control, and it is not vacuous: at DEBUG the crates
# underneath (matrix-sdk and its spans, hyper, eyeball) are extremely
# talkative - raising the catch-all level turns these two records into
# roughly a hundred. Their silence is the `warn` catch-all being enforced.
# Second clause is the control, and it is not vacuous. At DEBUG the crates underneath are
# extremely talkative, so raising the catch-all turns these two records into roughly a
# hundred. Their silence is the `warn` catch-all being enforced.
#
# A control on mxlink was tried first and is the trap here: mxlink happens to
# emit no DEBUG records at all on a first run, so asserting their absence
# passed just as happily with mxlink set to `debug`.
# The trap here: a control on mxlink was tried first, and mxlink emits no DEBUG records at
# all on a first run - so asserting their absence passed just as happily with mxlink set
# to `debug`.
- name: Assert the per-target logging levels reached the process
ansible.builtin.assert:
that:
@@ -150,10 +141,8 @@
vars:
matrix_bot_baibot_config: "{{ matrix_bot_baibot_config_file.content | b64decode | from_yaml }}"

# The role supports two mutually-exclusive authentication modes and refuses
# a configuration that sets both. This scenario uses the password mode, so
# the access-token keys must be rendered as nulls rather than omitted or
# left with a value.
# The role refuses a configuration that sets both authentication modes. This scenario uses
# password mode, so the access-token keys must render as nulls, not be omitted or set.
- name: Assert only the password authentication mode is rendered
ansible.builtin.assert:
that:
@@ -165,14 +154,12 @@
vars:
matrix_bot_baibot_config: "{{ matrix_bot_baibot_config_file.content | b64decode | from_yaml }}"

# The agent presets are the most involved templating in this role: a
# per-provider template is rendered to YAML, parsed, merged with an
# extension, and dropped into the list as a nested structure. This asserts
# the whole round trip, key by key.
# The agent presets are the most involved templating in this role: a per-provider template
# rendered to YAML, parsed, merged with an extension, nested into the list. Asserted as a
# whole round trip, key by key.
#
# No provider is ever contacted. baibot calls one only when a message asks an
# agent to do something, and the base URL here resolves nowhere on purpose -
# a scenario must not need an account with an AI provider.
# No provider is ever contacted. baibot calls one only when a message asks an agent to do
# something, and the base URL here resolves nowhere on purpose.
- name: Assert the statically-defined agent survived the provider templating
ansible.builtin.assert:
that:
@@ -215,10 +202,8 @@
{{ matrix_bot_baibot_role_defaults.matrix_bot_baibot_version }}
success_msg: "The running container is the version defaults/main.yml pins"

# The uid/gid come from outside the role (matrix-base supplies them in a real
# run, molecule-shared/playbook-context.yml here) and are deliberately not
# 1000, which the base image already uses - so this cannot pass by
# coincidence with whatever the image would have run as.
# The uid/gid come from outside the role and are deliberately not 1000, which the base
# image already uses, so this cannot pass by coinciding with the image's own user.
- name: Assert the container runs as the identity the playbook supplies
ansible.builtin.assert:
that:
@@ -228,10 +213,8 @@
({{ matrix_bot_baibot_container.stdout }})
success_msg: "The container runs as the uid/gid the playbook supplies"

# baibot keeps its session and crypto store here. The file existing proves
# the bind mount is writable by the user the container runs as - a
# read-only-root container whose data directory it could not write would
# never have got as far as logging in.
# baibot keeps its session and crypto store here. The file existing proves the bind mount
# is writable by the user the container runs as.
- name: Stat the session file baibot persists
ansible.builtin.stat:
path: "{{ matrix_bot_baibot_data_path }}/session.json"


+ 5
- 7
roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml Voir le fichier

@@ -3,11 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# The devture base roles carry the variables this role reads
# (`devture_systemd_docker_base_*`, `devture_playbook_help_*`), the same way
# they do when the playbook runs. `matrix-base` is deliberately NOT included:
# it does far more than this role needs, and the variables it would supply come
# from molecule-shared/playbook-context.yml instead.
# The devture base roles carry the variables this role reads, the same way they do when
# the playbook runs. `matrix-base` is deliberately NOT included: it does far more than this
# role needs, and what it would supply comes from molecule-shared/playbook-context.yml.
- name: Include roles for matrix-reminder-bot Molecule tests
hosts: all
become: true
@@ -26,8 +24,8 @@
loop_control:
loop_var: role_name

# The role installs the unit but does not start it - in the playbook that is
# `systemd_service_manager`'s job - so the scenario starts it here.
# The role installs the unit but does not start it; in the playbook that is
# `systemd_service_manager`'s job.
- name: Ensure matrix-reminder-bot is started
hosts: all
become: true


+ 14
- 20
roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml Voir le fichier

@@ -29,28 +29,25 @@ provisioner:
all:
matrix_bot_matrix_reminder_bot_container_network: matrix-reminder-bot-molecule

# Unlike the bridges, this bot is not an appservice: it logs into the
# homeserver as an ordinary user with a password. The stub prepare.yml
# stands up answers /_matrix/client/v3/login with an access token, which
# is all the bot needs to get past its login and into its sync loop.
# Unlike the bridges, this bot is not an appservice: it logs in as an ordinary user
# with a password. The stub answers /_matrix/client/v3/login with an access token,
# which is all the bot needs to reach its sync loop.
matrix_bot_matrix_reminder_bot_matrix_homeserver_url: http://matrix.molecule.local:8008

# Deliberately different from the role's default localpart
# (`bot.matrix-reminder-bot`), so verify.yml can tell what the role
# Different from the role's default localpart, so verify.yml can tell what the role
# rendered apart from what it would have rendered anyway.
matrix_bot_matrix_reminder_bot_matrix_user_id_localpart: molecule.reminder-bot
matrix_bot_matrix_reminder_bot_matrix_user_password: molecule_bot_password_4f2a91

# The role has no default here and refuses to run without one. Also
# different from the bot's own fallback (`Etc/UTC`), and it reaches the
# container twice - through the config file and through TZ on the unit.
# The role has no default here and refuses to run without one. Also different from the
# bot's own fallback, and it reaches the container twice: the config file and TZ.
matrix_bot_matrix_reminder_bot_reminders_timezone: Europe/Sofia

# The role and the bot both default to `!`.
matrix_bot_matrix_reminder_bot_command_prefix: "%%"

# Both lists default to off with no entries, so turning them on with
# entries of our own exercises the `_auto + _custom` composition.
# Both lists default to off with no entries, so turning them on exercises the
# `_auto + _custom` composition.
matrix_bot_matrix_reminder_bot_allowlist_enabled: true
matrix_bot_matrix_reminder_bot_allowlist_regexes_custom:
- "@molecule-allowed:molecule.local"
@@ -58,22 +55,19 @@ provisioner:
matrix_bot_matrix_reminder_bot_blocklist_regexes_custom:
- ".*:blocked.molecule.local"

# The device name is hardcoded in the role's config template, so
# overriding it is only possible through the extension mechanism. Doing
# it here means the merge of template + extension is tested too.
# The device name is hardcoded in the role's template, so overriding it is only
# possible through the extension mechanism - which tests that merge too.
matrix_bot_matrix_reminder_bot_configuration_extension_yaml: |
matrix:
device_name: Molecule Reminder Bot

# The SQLite database path is moved off the role's default (`bot.db`) so
# that verify.yml can assert the bot opened the path the role gave it,
# with the default name as a negative control.
# Moved off the role's default name so verify.yml can assert the bot opened the path
# the role gave it, with the default name as a negative control.
matrix_bot_matrix_reminder_bot_sqlite_database_path_local: /matrix/matrix-reminder-bot/data/molecule-reminders.db
matrix_bot_matrix_reminder_bot_sqlite_database_path_in_container: /data/molecule-reminders.db

# verify.yml runs as its own play, where the role's defaults are out of
# scope, so the paths it reads are pinned here as literals. They match
# what the role derives from `matrix_base_data_path`.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_bot_matrix_reminder_bot_base_path: /matrix/matrix-reminder-bot
matrix_bot_matrix_reminder_bot_config_path: /matrix/matrix-reminder-bot/config
matrix_bot_matrix_reminder_bot_data_path: /matrix/matrix-reminder-bot/data


+ 7
- 10
roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name, and Ansible resolves those
# through the passwd database - so they have to exist before it runs. In a
# real deployment `matrix-base` creates them.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -57,8 +56,8 @@
group: "{{ matrix_group_name }}"
mode: "0750"

# The role creates this network itself during converge, but the homeserver
# stub has to be on it before the bot starts, so it is created here first.
# The role creates this network itself during converge, but the stub has to be on it
# before the bot starts.
- name: Ensure the container network the role attaches to exists
ansible.builtin.command:
argv:
@@ -72,11 +71,9 @@
- matrix_bot_matrix_reminder_bot_molecule_network.rc != 0
- "'already exists' not in matrix_bot_matrix_reminder_bot_molecule_network.stderr"

# This bot is not an appservice - it logs in with the username and password
# the role rendered into its configuration, and retries every 15 seconds
# until that succeeds. The shared stub answers the login with an access
# token, which is enough to get it into its sync loop. Nothing is asserted
# about the stub itself; see molecule-shared/homeserver-stub.py.
# Not an appservice: it logs in with the username and password the role rendered, retrying
# every 15 seconds until that succeeds. The stub answers with an access token, which is
# enough to reach the sync loop. Nothing is asserted about the stub itself.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 34
- 48
roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml Voir le fichier

@@ -3,13 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: matrix-reminder-bot starts on the configuration the role
# rendered, logs into a homeserver as the user the role gave it, opens the
# database at the path the role gave it, and is the version the role pins.
# Proves matrix-reminder-bot starts on the configuration the role rendered, logs in as the
# user the role gave it, opens the database at the path the role gave it, and is the version
# the role pins.
#
# The bot has no HTTP surface of its own to probe, so the evidence is what it
# says about itself in the journal plus what it left on disk. It does NOT set
# real reminders and never will. See docs/molecule-testing.md.
# The bot has no HTTP surface to probe, so the evidence is what it says about itself in the
# journal plus what it left on disk. It does NOT set real reminders. See docs/molecule-testing.md.
- name: Verify matrix-reminder-bot
hosts: all
become: true
@@ -23,10 +22,9 @@
matrix_bot_matrix_reminder_bot_molecule_container_user: "{{ matrix_user_uid }}:{{ matrix_user_gid }}"

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.
# Read from the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what defaults/main.yml 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"
@@ -41,12 +39,10 @@
delay: 5
failed_when: false

# `Restart=always` means a bot crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. The
# config file is parsed before the bot's own catch-all retry loop starts, so
# anything wrong in what the role rendered shows up here as restarts.
# 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.
# `Restart=always` means a bot crash-looping on unreadable config still reports `active`,
# so the restart counter is checked too. The config file is parsed before the bot's own
# catch-all retry loop starts, so anything wrong in what the role rendered shows up here
# as restarts. Asserted `is defined` because `| int` turns a missing property into 0.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -60,14 +56,12 @@
automatic restart(s)
success_msg: "matrix-bot-matrix-reminder-bot.service is active and has not restarted"

# The unit runs `docker start --attach`, so the container's output is in the
# journal despite `--log-driver=none`. That is the only thing this bot
# reports about itself - it serves nothing over HTTP.
# The unit runs `docker start --attach`, so the container's output is in the journal
# despite `--log-driver=none`. It is the only thing this bot reports about itself.
#
# Filtered rather than tailed: the startup lines are the oldest ones in the
# journal, so a `--lines=N` tail would lose them behind anything the bot
# logs later, and reading the journal whole would pull an unbounded amount
# of text into a variable. The filter keeps the failure line too, so the
# Filtered rather than tailed: startup lines are the OLDEST in the journal, so a
# `--lines=N` tail loses them behind anything logged later, and reading it whole pulls
# unbounded text into a variable. The filter keeps the failure line too, so the
# "did not fail to log in" assertion below still has something to see.
- name: Wait for the bot to report that it finished starting up
ansible.builtin.shell:
@@ -83,10 +77,9 @@
delay: 5
failed_when: false

# "Logged in as ..." is only reached after the bot's login call came back as
# something other than a LoginError, so this is the whole chain at once: the
# homeserver URL, the user ID and the password the role rendered were good
# enough for a real login round-trip against the stub.
# "Logged in as ..." is only reached once the login call returned something other than a
# LoginError, so this covers the whole chain at once: homeserver URL, user ID and password
# were all good enough for a real login round-trip.
- name: Assert the bot logged in as the user the role configured
ansible.builtin.assert:
that:
@@ -99,9 +92,8 @@
success_msg: >-
The bot logged in as {{ matrix_bot_matrix_reminder_bot_molecule_user_id }} and finished starting up

# The role picks the storage engine (SQLite here, Postgres otherwise) by
# building the connection string the bot parses, and the bot names the type
# it settled on once the database is open.
# The role picks the storage engine by building the connection string the bot parses,
# and the bot names the type it settled on once the database is open.
- name: Assert the bot opened the database engine the role selected
ansible.builtin.assert:
that:
@@ -114,9 +106,8 @@
src: "{{ matrix_bot_matrix_reminder_bot_config_path }}/config.yaml"
register: matrix_bot_matrix_reminder_bot_config_file

# Every one of these differs from both the role's defaults and the bot's own
# fallbacks, so their presence means the role rendered this file rather than
# the values coinciding with what would have happened anyway.
# Every one differs from both the role's defaults and the bot's own fallbacks, so their
# presence means the role rendered this file rather than coinciding with it.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
@@ -133,9 +124,8 @@
vars:
matrix_bot_matrix_reminder_bot_config_rendered: "{{ matrix_bot_matrix_reminder_bot_config_file.content | b64decode }}"

# `device_name` is hardcoded in the role's config template, so this value can
# only be there if `..._configuration_extension_yaml` was merged over the
# template rather than ignored.
# `device_name` is hardcoded in the role's template, so this value can only be here if
# `..._configuration_extension_yaml` was merged over it rather than ignored.
- name: Assert the configuration extension was merged over the template
ansible.builtin.assert:
that:
@@ -148,9 +138,8 @@
vars:
matrix_bot_matrix_reminder_bot_config_rendered: "{{ matrix_bot_matrix_reminder_bot_config_file.content | b64decode }}"

# The bot has no HTTP surface, so where its database landed is the evidence
# that the storage configuration reached the running process rather than
# merely the file on disk.
# With no HTTP surface, where the database landed is the evidence that the storage
# configuration reached the running process and not merely the file on disk.
- name: Stat the database at the path the scenario configured
ansible.builtin.stat:
path: "{{ matrix_bot_matrix_reminder_bot_sqlite_database_path_local }}"
@@ -168,9 +157,8 @@
success_msg: >-
The database is at the configured path, owned by {{ matrix_user_uid }}:{{ matrix_user_gid }}

# A negative control for the assertion above: the role's own default
# database name must NOT appear, or a file at the configured path would not
# prove the configuration reached the process.
# Negative control for the assertion above: the role's own default database name must NOT
# appear, or a file at the configured path would prove nothing.
- name: Stat the database name the role would have used by default
ansible.builtin.stat:
path: "{{ matrix_bot_matrix_reminder_bot_data_path }}/bot.db"
@@ -186,9 +174,8 @@
configuration reached the bot
success_msg: "Only the configured database path was used"

# matrix-nio writes its encryption store here once a login has succeeded, so
# a populated directory means the bot could use the store path the role
# created for it inside an otherwise read-only container.
# matrix-nio writes its encryption store here once login succeeds, so a populated directory
# means the bot could use the store path the role created inside a read-only container.
- name: List the encryption store the role created
ansible.builtin.find:
paths: "{{ matrix_bot_matrix_reminder_bot_data_store_path }}"
@@ -216,9 +203,8 @@
register: matrix_bot_matrix_reminder_bot_container
changed_when: false

# The timezone reaches the container twice - through the config file checked
# above and through TZ on the unit - and the uid/gid come from the playbook
# context rather than from anything the image would pick on its own.
# The timezone reaches the container twice, through the config file checked above and
# through TZ on the unit. The uid/gid come from the playbook context, not from the image.
- name: Assert the container runs as the role's user with the configured timezone
ansible.builtin.assert:
that:


+ 11
- 13
roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml Voir le fichier

@@ -29,31 +29,29 @@ provisioner:
all:
matrix_bridge_heisenbridge_container_network: heisenbridge-molecule

# The stub prepare.yml stands up. Heisenbridge talks to it while
# starting; it is not a real homeserver and nothing is asserted about it.
# The stub prepare.yml stands up. Not a real homeserver, and nothing is asserted
# about it.
matrix_bridge_heisenbridge_homeserver_url: http://matrix.molecule.local:8008

matrix_bridge_heisenbridge_appservice_token: molecule_as_token_4f2a91

# Heisenbridge refuses to start without an owner - it is the Matrix user
# allowed to administer the bridge.
# Heisenbridge refuses to start without an owner: the Matrix user allowed to
# administer the bridge.
matrix_bridge_heisenbridge_owner: "@molecule-admin:molecule.local"

# Deliberately different from the role's defaults, so verify.yml can tell
# what the role rendered apart from what heisenbridge would have chosen.
# Different from the role's defaults, so verify.yml can tell what the role rendered
# apart from what heisenbridge would have chosen.
matrix_bridge_heisenbridge_path_prefix: /molecule-heisenbridge

# identd binds host port 113, which would collide with anything else on
# the machine and is not what this scenario is proving.
# identd binds host port 113, which would collide with anything else on the machine.
matrix_bridge_heisenbridge_identd_enabled: false

# Traefik is not deployed here, so the labels the role would render for
# it are switched off and their absence is asserted instead.
# Traefik is not deployed here, so the labels the role would render for it are
# switched off and their absence is asserted instead.
matrix_bridge_heisenbridge_container_labels_traefik_enabled: false

# verify.yml runs as its own play, where role defaults are out of scope.
# Unlike the mautrix bridges, heisenbridge keeps everything directly
# under its base path rather than in config/ and data/ subdirectories.
# verify.yml runs as its own play, where role defaults are out of scope. Unlike the
# mautrix bridges, heisenbridge keeps everything directly under its base path.
matrix_bridge_heisenbridge_base_path: /matrix/heisenbridge
env:
# Workaround for https://github.com/ansible/molecule/issues/4391


+ 4
- 5
roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -70,8 +69,8 @@
- heisenbridge_molecule_network.rc != 0
- "'already exists' not in heisenbridge_molecule_network.stderr"

# The bridge contacts the homeserver as it starts. It is not being asked to
# bridge anything - see molecule-shared/homeserver-stub.py.
# The bridge contacts the homeserver as it starts. It is not being asked to bridge
# anything. See molecule-shared/homeserver-stub.py.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 10
- 14
roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml Voir le fichier

@@ -3,9 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: heisenbridge starts, reads the registration the role
# rendered, and is the version the role pins. It does NOT connect to IRC and
# never will. See docs/molecule-testing.md.
# Proves heisenbridge starts, reads the registration the role rendered, and is the version
# the role pins. It does NOT connect to IRC. See docs/molecule-testing.md.
- name: Verify heisenbridge
hosts: all
become: true
@@ -29,8 +28,8 @@
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too.
# `Restart=always` means a bridge crash-looping on unreadable config still reports
# `active`, so the restart counter is checked too.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -44,16 +43,14 @@
automatic restart(s)
success_msg: "matrix-heisenbridge.service is active and has not restarted"

# Heisenbridge keeps everything directly under its base path rather than in
# config/ and data/ subdirectories the way the mautrix bridges do.
# Heisenbridge keeps everything directly under its base path, unlike the mautrix bridges.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_heisenbridge_base_path }}/registration.yaml"
register: heisenbridge_registration_file

# The token and the URL both differ from anything heisenbridge would pick on
# its own, so their presence means the role rendered this file rather than
# the bridge generating one.
# Token and URL both differ from anything heisenbridge would pick on its own, so their
# presence means the role rendered this file rather than the bridge generating one.
- name: Assert the registration carries the scenario's token and namespace
ansible.builtin.assert:
that:
@@ -64,8 +61,7 @@
vars:
heisenbridge_registration_rendered: "{{ heisenbridge_registration_file.content | b64decode }}"

# The owner is what heisenbridge is told to accept administration from, and
# the role passes it on the command line rather than through a config file,
# The role passes the owner on the command line rather than through a config file,
# so the unit is where it can be checked.
- name: Read the systemd unit the role rendered
ansible.builtin.slurp:
@@ -82,8 +78,8 @@
vars:
heisenbridge_unit_rendered: "{{ heisenbridge_unit_file.content | b64decode }}"

# identd binds host port 113 when enabled, and this scenario turns it off.
# Asserting its absence keeps the default from silently becoming "on".
# identd binds host port 113 when enabled. Asserting its absence keeps the default from
# silently becoming "on".
- name: Assert identd is not published while it is disabled
ansible.builtin.assert:
that:


+ 25
- 35
roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml Voir le fichier

@@ -29,67 +29,57 @@ provisioner:
all:
matrix_bridge_hookshot_container_network: hookshot-molecule

# The stub prepare.yml stands up. Hookshot contacts it while starting;
# it is not a real homeserver and nothing is asserted about it.
# The stub prepare.yml stands up. Not a real homeserver, and nothing is asserted
# about it.
matrix_bridge_hookshot_homeserver_address: http://matrix.molecule.local:8008

# Appservice tokens. These are what Hookshot and the homeserver would
# authenticate to each other with; here they only have to reach the
# rendered configuration and the registration file.
# Here these only have to reach the rendered configuration and the registration.
matrix_bridge_hookshot_appservice_token: molecule_as_token_4f2a91
matrix_bridge_hookshot_homeserver_token: molecule_hs_token_9b3e77

# Deliberately different from the role's default (`hookshot`), so the
# registration's sender_localpart can only have come from the role.
# prepare.yml tells the homeserver stub to claim this same user id.
# Different from the role's default, so the registration's sender_localpart can only
# have come from the role. prepare.yml tells the stub to claim this same user id.
matrix_bridge_hookshot_bot_localpart: molecule-hookshot

# Hookshot's HTTP surface is the point of this scenario. Every port
# below differs from BOTH the role's default and Hookshot's own, so an
# answer on one of them can only mean the role's configuration reached
# the process. verify.yml also asserts that nothing answers on the
# defaults these replace (9993 appservice, 9000 webhooks, 9001 metrics).
# Hookshot's HTTP surface is the point of this scenario. Every port below differs
# from BOTH the role's default and Hookshot's own, so an answer can only mean the
# role's configuration reached the process. verify.yml also asserts nothing answers
# on the defaults these replace.
matrix_bridge_hookshot_appservice_port: 9772
matrix_bridge_hookshot_webhook_port: 9741

# Off in the role's defaults. Enabling it makes the role render a second
# entry in Hookshot's `listeners` list, which is the cheapest listener to
# assert *content* on: /metrics answers in a format nothing else would.
# Off in the role's defaults. Enabling it renders a second entry in the `listeners`
# list, and /metrics is the cheapest listener to assert *content* on.
matrix_bridge_hookshot_metrics_enabled: true
matrix_bridge_hookshot_metrics_port: 9752

# On in the role's defaults. Turned off here so that the absence of a
# listener can be asserted too - the widgets port staying closed is what
# tells "the role rendered the listener list" apart from "Hookshot binds
# everything anyway".
# On in the role's defaults. Off here so the absence of a listener can be asserted:
# the widgets port staying closed is what tells "the role rendered the listener list"
# apart from "Hookshot binds everything anyway".
matrix_bridge_hookshot_widgets_enabled: false

# No third-party service is configured in this scenario - see the header
# of verify.yml. GitLab is the one the role enables by default, so it is
# explicitly switched off and its absence from the rendered config and
# registration is asserted.
# No third-party service is configured here; see the header of verify.yml.
# GitLab is the one the role enables by default, so it is explicitly switched off.
matrix_bridge_hookshot_gitlab_enabled: false

# The generic webhooks listener is the only part of Hookshot that needs
# no account anywhere, so it is what this scenario exercises live. The
# prefix differs from the role's default (`_webhooks_`).
# The generic webhooks listener needs no account anywhere, so it is what this
# scenario exercises live. The prefix differs from the role's default.
matrix_bridge_hookshot_generic_userIdPrefix: _molecule_hook_ # noqa var-naming

# Neither the role's default (600) nor Hookshot's own (600).
matrix_bridge_hookshot_feeds_pollIntervalSeconds: 907 # noqa var-naming

# The role defaults to `warn`; Hookshot itself defaults to `info`. This
# is a third value, so finding it in config.yml cannot be a coincidence.
# The role defaults to `warn`, Hookshot itself to `info`. A third value, so finding
# it in config.yml cannot be a coincidence.
matrix_bridge_hookshot_logging_level: debug

# Traefik is not deployed here, so the labels the role would render for
# it are switched off and their absence is asserted instead.
# Traefik is not deployed here, so the labels the role would render for it are
# switched off and their absence is asserted instead.
matrix_bridge_hookshot_container_labels_traefik_enabled: false

# verify.yml runs as its own play, where role defaults are out of scope,
# so what it reads is pinned here. These two match the role's own
# defaults on purpose - they name things (a path, a container) rather
# than configure them, and nothing is asserted *about* them.
# verify.yml runs as its own play, where role defaults are out of scope, so what it
# reads is pinned here. These two match the role's own defaults on purpose: they name
# things rather than configure them, and nothing is asserted *about* them.
matrix_bridge_hookshot_base_path: /matrix/hookshot
matrix_bridge_hookshot_identifier: matrix-hookshot
env:


+ 2
- 3
roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"


+ 42
- 52
roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml Voir le fichier

@@ -3,15 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: Hookshot starts, accepts the config.yml and registration.yml
# the role rendered, and opens exactly the HTTP listeners that configuration
# described - on the ports the role put there, and not on the ones it did not.
# Proves Hookshot starts on the config.yml and registration.yml the role rendered,
# and opens exactly the HTTP listeners that configuration described - on the ports
# the role put there, and not on the ones it did not.
#
# What it deliberately does NOT do: configure GitHub, GitLab, Jira or Figma.
# Every one of those needs an account and a credential on a third-party service,
# which is the line where a scenario stops testing this repository and starts
# testing a fake (see docs/molecule-testing.md). The generic webhooks listener
# needs no credential from anyone, so it is the one that gets exercised live.
# Deliberately does NOT configure GitHub, GitLab, Jira or Figma. Each needs an account
# and a credential on a third-party service, which is where a scenario stops testing this
# repository and starts testing a fake (see docs/molecule-testing.md). The generic webhooks
# listener needs no credential from anyone, so it is the one exercised live.
- name: Verify hookshot
hosts: all
become: true
@@ -21,11 +20,10 @@
gather_facts: false

tasks:
# Read from the role's own defaults rather than pinned in molecule.yml, so
# the version assertion below compares the running image against what the
# role ships instead of against the scenario itself. The default ports come
# from here for the same reason: the "these ports stay closed" assertion is
# only meaningful against the ports the role would otherwise have used.
# From the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what the role ships, not against the
# scenario. The default ports come from here for the same reason: "these ports stay
# closed" is only meaningful against the ports the role would otherwise have used.
- name: Load the role's defaults under a separate name
ansible.builtin.include_vars:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
@@ -40,10 +38,9 @@
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. It is
# asserted `is defined` because `| int` turns a missing property into 0 and
# would pass vacuously.
# `Restart=always` means a bridge crash-looping on unreadable config still reports
# `active`, so the restart counter is checked too. Asserted `is defined` because
# `| int` turns a missing property into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -70,9 +67,8 @@
ansible.builtin.set_fact:
hookshot_config: "{{ hookshot_config_file.content | b64decode | from_yaml }}"

# Each of these differs from what Hookshot would use on its own AND from
# what the role defaults to, so finding them here means the role's
# configuration is what Hookshot is running on rather than a coincidence.
# Each differs from what Hookshot would use on its own AND from the role's defaults,
# so finding them here rules out a coincidence.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
@@ -86,10 +82,9 @@
fail_msg: "The rendered configuration does not carry the scenario's values"
success_msg: "The rendered configuration carries the scenario's values"

# Hookshot's `listeners` list is the role's own construction: it decides
# which resources get a port at all, from a handful of independent switches.
# Getting this wrong is invisible in a "did it start" test, which is why it
# is asserted as a whole rather than key by key.
# The `listeners` list is the role's own construction, assembled from a handful of
# independent switches. Getting it wrong is invisible in a "did it start" test,
# hence asserting the whole shape rather than key by key.
- name: Assert the role rendered exactly the listeners the scenario asked for
ansible.builtin.assert:
that:
@@ -103,9 +98,8 @@
vars:
hookshot_listener_ports: "{{ hookshot_config.listeners | map(attribute='port') | map('int') | list }}"

# No third-party service is configured here, so none of their sections may
# appear. GitLab is the interesting one: the role turns it ON by default, so
# its absence is what proves the scenario's switch reached the template.
# GitLab is the interesting one: the role turns it ON by default, so its absence
# proves the scenario's switch reached the template.
- name: Assert no third-party service section was rendered
ansible.builtin.assert:
that:
@@ -131,9 +125,8 @@
# The rendered registration
# ------------------------------------------------------------------

# The registration file is the half of the appservice handshake the
# homeserver reads. The role generates it, Hookshot only consumes it, so it
# is worth checking on its own.
# The role generates the registration; Hookshot only consumes it. Worth checking
# on its own, as it is the half of the handshake the homeserver reads.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_hookshot_base_path }}/registration.yml"
@@ -143,9 +136,8 @@
ansible.builtin.set_fact:
hookshot_registration: "{{ hookshot_registration_file.content | b64decode | from_yaml }}"

# `url` is where the homeserver would push transactions, and the role builds
# it out of the container name and the appservice port. It has to agree with
# `bridge.port` in config.yml or the two halves would silently disagree.
# `url` is built from the container name and the appservice port. It has to agree
# with `bridge.port` in config.yml, or the two halves silently disagree.
- name: Assert the registration carries the scenario's tokens, bot and callback URL
ansible.builtin.assert:
that:
@@ -156,9 +148,9 @@
fail_msg: "The appservice registration does not carry the scenario's tokens, bot and callback URL"
success_msg: "The appservice registration carries the scenario's tokens, bot and callback URL"

# The user namespace is derived from the generic webhook prefix, and the
# GitLab namespace is conditional on the service being enabled - so this
# checks that the two switches reach the registration, not just config.yml.
# The user namespace derives from the generic webhook prefix, and the GitLab namespace
# is conditional on that service being enabled. Checks both switches reach the
# registration, not just config.yml.
- name: Assert the registration namespaces follow the enabled services
ansible.builtin.assert:
that:
@@ -176,8 +168,8 @@
# The listeners, live
# ------------------------------------------------------------------

# A helper container is used because the role publishes no host port, exactly
# as in a real deployment; see docs/molecule-testing.md.
# A helper container, because the role publishes no host port - exactly as in a real
# deployment. See docs/molecule-testing.md.
- name: Wait for the webhooks listener to answer on the port the role configured
ansible.builtin.command:
argv:
@@ -205,11 +197,10 @@
delay: 5
failed_when: false

# Hookshot answers an unknown webhook id from its generic-webhook handler,
# with a JSON body no other component would produce. An Express "Cannot POST"
# page here would mean the port is Hookshot's but the generic webhooks
# service was never mounted on it; a refused connection would mean the
# listener the role described was never opened at all.
# An unknown webhook id draws a JSON body from the generic-webhook handler that no
# other component would produce. An Express "Cannot POST" page would mean the port is
# Hookshot's but the generic webhooks service was never mounted on it; a refused
# connection would mean the listener was never opened at all.
- name: Assert the generic webhooks service is mounted on that listener
ansible.builtin.assert:
that:
@@ -222,9 +213,8 @@
({{ hookshot_webhooks_probe.stdout | default('no output') }})
success_msg: "The generic webhooks service answers on the port the role configured"

# Metrics are OFF in the role's defaults, so this listener exists only
# because the scenario asked for it - and /metrics answers in a format
# nothing else on that port could have produced.
# Metrics are OFF in the role's defaults, so this listener exists only because the
# scenario asked for it.
- name: Probe the metrics listener on the port the role configured
ansible.builtin.command:
argv:
@@ -253,8 +243,8 @@
metrics ({{ hookshot_metrics_probe.stdout | default('no output') | truncate(200) }})
success_msg: "The metrics listener serves Hookshot's own metrics"

# The appservice port is not in `listeners` - it comes from `bridge.port` -
# so it is a separate socket, opened by a separate part of the config.
# The appservice port is not in `listeners`; it comes from `bridge.port`.
# A separate socket, opened by a separate part of the config.
- name: Probe the appservice port the role configured
ansible.builtin.command:
argv:
@@ -284,10 +274,10 @@
({{ hookshot_appservice_probe.stdout | default('no output') }})
success_msg: "The appservice API answers on the port the role configured"

# The other half of the story. Every port above is one the scenario chose;
# these are the ones the role and Hookshot would have used if the scenario's
# configuration had never reached the process. If any of them answers, then
# a passing probe above proves much less than it looks like it does.
# The other half of the story. Every port above is one the scenario chose; these are
# the ones the role and Hookshot would have used had the scenario's configuration never
# reached the process. If any of them answers, the probes above prove much less than
# they appear to.
- name: Probe the ports the role's defaults would have used
ansible.builtin.command:
argv:


+ 17
- 27
roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml Voir le fichier

@@ -29,51 +29,41 @@ provisioner:
all:
matrix_bridge_mautrix_discord_container_network: mautrix-discord-molecule

# The homeserver stub prepare.yml stands up. The bridge contacts it
# while starting; it is not a real homeserver and nothing is asserted
# about it. There is deliberately no Discord on the other side either -
# see docs/molecule-testing.md.
# The stub prepare.yml stands up. Not a real homeserver, and nothing is asserted
# about it. There is deliberately no Discord on the other side either.
matrix_bridge_mautrix_discord_homeserver_address: http://matrix.molecule.local:8008

# sqlite keeps the scenario to one container. The role only requires a
# database hostname when the engine is postgres, and testing which
# database engine the bridge can talk to is not what this proves.
# sqlite keeps the scenario to one container. Which database engine the bridge can
# talk to is not what this proves.
matrix_bridge_mautrix_discord_database_engine: sqlite

# Appservice tokens. These are what the bridge and homeserver would
# authenticate to each other with; here they only have to reach the
# rendered configuration and the registration file.
# Here these only have to reach the rendered configuration and the registration.
matrix_bridge_mautrix_discord_appservice_token: molecule_as_token_d15c07
matrix_bridge_mautrix_discord_homeserver_token: molecule_hs_token_a4e2b8

# Deliberately different from the role's defaults, so verify.yml can
# tell what the role rendered apart from what the bridge would have
# defaulted to on its own.
# Different from the role's defaults, so verify.yml can tell what the role rendered
# apart from what the bridge would have chosen.
matrix_bridge_mautrix_discord_appservice_bot_username: molecule-discordbot
matrix_bridge_mautrix_discord_homeserver_domain: molecule.local
matrix_bridge_mautrix_discord_bridge_command_prefix: "!molecule-discord"
# The role defaults to `warn`; the bridge's own shipped configuration
# uses `debug`. `info` is neither.
# The role defaults to `warn`, the bridge's own shipped configuration to `debug`.
# `info` is neither.
matrix_bridge_mautrix_discord_logging_level: info

# Unlike most bridge roles here, mautrix-discord *requires* a public
# address: `validate_config.yml` fails without
# `matrix_bridge_mautrix_discord_bridge_public_address`, which is
# derived from these three. Discord fetches avatars over it in relay
# mode; nothing reaches it in this scenario, but it has to be set for
# the role to run at all.
# Unlike most bridge roles here, mautrix-discord *requires* a public address:
# `validate_config.yml` fails without one, and it is derived from these three.
# Discord fetches avatars over it in relay mode. Nothing reaches it in this scenario,
# but it has to be set for the role to run at all.
#
# A non-`/` path prefix and a non-default scheme are chosen so the
# avatar-proxy labels verify.yml reads can only look the way they do if
# the role composed them from these values.
# A non-`/` path prefix and a non-default scheme are chosen so the avatar-proxy labels
# verify.yml reads can only look the way they do if the role composed them.
matrix_bridge_mautrix_discord_hostname: discord.molecule.local
matrix_bridge_mautrix_discord_path_prefix: /discord-bridge
matrix_bridge_mautrix_discord_scheme: http
matrix_bridge_mautrix_discord_bridge_avatar_proxy_key: molecule_avatar_proxy_key_7c1d

# verify.yml runs as its own play, where role defaults are out of scope,
# so the paths it reads are pinned here as literals matching what the
# role derives from matrix_base_data_path.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_bridge_mautrix_discord_base_path: /matrix/mautrix-discord
matrix_bridge_mautrix_discord_config_path: /matrix/mautrix-discord/config
matrix_bridge_mautrix_discord_data_path: /matrix/mautrix-discord/data


+ 4
- 6
roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -70,9 +69,8 @@
- mautrix_discord_molecule_network.rc != 0
- "'already exists' not in mautrix_discord_molecule_network.stderr"

# The bridge contacts the homeserver as it starts, and refuses to run if
# /whoami does not name the bot user it was configured as - see
# molecule-shared/homeserver-stub.py.
# The bridge contacts the homeserver as it starts and refuses to run if /whoami does not
# name the bot user it was configured as. See molecule-shared/homeserver-stub.py.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 30
- 40
roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml Voir le fichier

@@ -3,13 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: the bridge starts, reads the configuration and registration
# the role rendered, opens its appservice port, and is the version the role
# pins. It also proves the role composed the avatar-proxy labels out of the
# hostname, scheme and path prefix it was given. It does NOT bridge anything -
# there is no Discord on the other side and there is deliberately never going to
# be one, because that would need a Discord account. See
# docs/molecule-testing.md.
# Proves the bridge starts, reads the configuration and registration the role rendered, opens
# its appservice port, is the version the role pins, and composed the avatar-proxy labels out
# of the hostname, scheme and path prefix it was given.
#
# It does NOT bridge anything: there is no Discord on the other side, and deliberately never
# will be, because that would need a Discord account. See docs/molecule-testing.md.
- name: Verify mautrix-discord
hosts: all
become: true
@@ -19,17 +18,15 @@
gather_facts: false

vars:
# The role derives this from scheme + hostname + path prefix, and role
# defaults are out of scope in this play, so it is recomposed here from the
# same three values the scenario pinned in molecule.yml.
# The role derives this from scheme + hostname + path prefix. Role defaults are out of
# scope in this play, so it is recomposed from the same three values molecule.yml pinned.
mautrix_discord_expected_public_address: >-
{{ matrix_bridge_mautrix_discord_scheme }}://{{ matrix_bridge_mautrix_discord_hostname }}{{ matrix_bridge_mautrix_discord_path_prefix }}
mautrix_discord_expected_avatar_proxy_path_prefix: "{{ matrix_bridge_mautrix_discord_path_prefix }}/mautrix-discord/avatar"

tasks:
# Read from the role's own defaults rather than pinned in molecule.yml, so
# the version assertion below compares the running image against what the
# role ships instead of against the scenario itself.
# From the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what the role ships, not the scenario.
- name: Load the role's defaults under a separate name
ansible.builtin.include_vars:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
@@ -44,10 +41,9 @@
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. It is
# asserted `is defined` because `| int` turns a missing property into 0 and
# would pass vacuously.
# `Restart=always` means a bridge crash-looping on unreadable config still reports
# `active`, so the restart counter is checked too. Asserted `is defined` because
# `| int` turns a missing property into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -61,9 +57,8 @@
automatic restart(s)
success_msg: "matrix-mautrix-discord.service is active and has not restarted"

# The appservice port is the bridge's own listener, the one a homeserver
# would push transactions to. It opening at all means the bridge got through
# reading its configuration and setting itself up.
# The appservice listener is where a homeserver would push transactions. It opening at all
# means the bridge got through reading its configuration and setting itself up.
- name: Wait for the bridge to open its appservice port
ansible.builtin.command:
argv:
@@ -99,13 +94,12 @@
src: "{{ matrix_bridge_mautrix_discord_config_path }}/config.yaml"
register: mautrix_discord_config_file

# Each of these differs from what the bridge would use on its own, so their
# presence means the role's configuration is what the bridge is running on
# rather than something that happened to agree with it. The public address
# in particular is composed by the role out of three separate variables.
# Each differs from what the bridge would use on its own, so their presence rules out a
# coincidence. The public address in particular is composed by the role out of three
# separate variables.
#
# Asserted against the parsed document rather than by substring, so a value
# landing under the wrong key cannot pass.
# Asserted against the parsed document rather than by substring, so a value landing under
# the wrong key cannot pass.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
@@ -124,10 +118,8 @@
vars:
mautrix_discord_config: "{{ mautrix_discord_config_file.content | b64decode | from_yaml }}"

# The registration file is the half of the appservice handshake the
# homeserver reads, and it is generated by the role rather than by the
# bridge, so it is worth checking on its own. `sender_localpart` is the
# role's own `_bot_` prefixing, not something the bridge would produce.
# The role generates the registration; the bridge only consumes it. `sender_localpart`
# carries the role's own `_bot_` prefixing, not something the bridge would produce.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_mautrix_discord_config_path }}/registration.yaml"
@@ -147,9 +139,8 @@
mautrix_discord_registration: "{{ mautrix_discord_registration_file.content | b64decode | from_yaml }}"
mautrix_discord_bot_user_regex: "^@{{ matrix_bridge_mautrix_discord_appservice_bot_username | regex_escape }}:{{ matrix_bridge_mautrix_discord_homeserver_domain | regex_escape }}$"

# sqlite was chosen in molecule.yml, so the bridge should have created its
# database under the role's data path. This is the cheap proof that the data
# path reached the process and is writable by the uid the role runs it as.
# Cheap proof that the data path reached the process and is writable by the uid the role
# runs it as.
- name: Look for the bridge's sqlite database under the role's data path
ansible.builtin.stat:
path: "{{ matrix_bridge_mautrix_discord_data_path }}/mautrix-discord.db"
@@ -188,9 +179,9 @@
{{ mautrix_discord_role_defaults.matrix_bridge_mautrix_discord_version }}
success_msg: "The running container is the version defaults/main.yml pins"

# The avatar proxy is this role's own reverse-proxy wiring: the labels only
# appear because a public address was configured, and their hostname and
# path prefix are composed by the role rather than copied from a variable.
# The avatar proxy is this role's own reverse-proxy wiring. The labels only appear because
# a public address was configured, and the role composes their hostname and path prefix
# rather than copying them from a variable.
- name: Read the labels the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_mautrix_discord_base_path }}/labels"
@@ -211,10 +202,9 @@
vars:
mautrix_discord_labels_rendered: "{{ mautrix_discord_labels.content | b64decode }}"

# The label file is fed to `docker create --label-file`, so a label the role
# renders wrongly is not merely cosmetic - it would stop the container from
# being created at all. Reading them back off the running container proves
# Docker accepted them.
# The label file is fed to `docker create --label-file`, so a wrongly rendered label is
# not cosmetic: it stops the container being created at all. Reading them back off the
# running container proves Docker accepted them.
- name: Read the labels Docker attached to the running container
ansible.builtin.command:
argv:


+ 21
- 36
roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml Voir le fichier

@@ -29,66 +29,51 @@ provisioner:
all:
matrix_bridge_mautrix_meta_messenger_container_network: mautrix-meta-messenger-molecule

# The homeserver stub prepare.yml stands up. The bridge contacts it
# while starting; it is not a real homeserver and nothing is asserted
# The stub prepare.yml stands up. Not a real homeserver, and nothing is asserted
# about it.
matrix_bridge_mautrix_meta_messenger_homeserver_address: http://matrix.molecule.local:8008
matrix_bridge_mautrix_meta_messenger_homeserver_domain: molecule.local

# sqlite keeps the scenario to one container. The role only requires a
# database hostname when the engine is postgres, and testing which
# database engine the bridge can talk to is not what this proves.
# sqlite keeps the scenario to one container. Which database engine the bridge can
# talk to is not what this proves.
matrix_bridge_mautrix_meta_messenger_database_engine: sqlite3-fk-wal

# The URI the role derives for `sqlite3-fk-wal` is deliberately NOT
# overridden here. It used to build `sqlite:///` + the in-container path,
# which go-sqlite3 takes as a plain filename rather than parsing as a URL,
# so the bridge died at startup with `unable to open database file`. This
# scenario is what caught it. Leaving the role's own value in place is
# what keeps it caught.
# The URI the role derives is deliberately NOT overridden here. It used to build
# `sqlite:///` + the in-container path, which go-sqlite3 takes as a plain filename
# rather than parsing as a URL, so the bridge died at startup. This scenario caught
# that, and leaving the role's own value in place is what keeps it caught.

# Appservice tokens. These are what the bridge and homeserver would
# authenticate to each other with; here they only have to reach the
# rendered configuration and the registration file.
# Here these only have to reach the rendered configuration and the registration.
matrix_bridge_mautrix_meta_messenger_appservice_token: molecule_meta_as_token_5c81de
matrix_bridge_mautrix_meta_messenger_homeserver_token: molecule_meta_hs_token_a70f24

# The one variable that makes this role family unusual: a single
# upstream codebase serves several Meta networks, and this variable is
# what picks which one. It reaches the rendered configuration in several
# places at once - the appservice id, the ghost username prefix, the bot
# displayname and the bridge's `tor` switch - so a value other than the
# role's default `messenger` is what tells verify.yml that the role
# propagated the choice rather than everything merely agreeing by
# accident. `facebook-tor` is the only one of the three modes that also
# flips a boolean in the configuration, which is why it is the one used.
# What makes this role family unusual: one upstream codebase serves several Meta
# networks, and this variable picks which. It reaches the rendered configuration in
# four places at once - appservice id, ghost username prefix, bot displayname and the
# bridge's `tor` switch - so a value other than the role's default is what tells
# verify.yml the role propagated the choice rather than everything agreeing by accident.
#
# Nothing logs in during the scenario, so the bridge never opens a
# connection to Meta (over Tor or otherwise). See docs/molecule-testing.md
# for why a scenario stops short of that.
# `facebook-tor` is used because it is the only mode that also flips a boolean.
# Nothing logs in during the scenario, so the bridge never connects to Meta.
matrix_bridge_mautrix_meta_messenger_meta_mode: facebook-tor

# Deliberately different from the role's defaults (`messengerbot`,
# `!fb`, `(FB)`, `warn`) and from what the bridge would pick on its own,
# so verify.yml can tell what the role rendered apart from a
# coincidence.
# Different from the role's defaults and from what the bridge would pick on its own,
# so verify.yml can tell what the role rendered apart from a coincidence.
matrix_bridge_mautrix_meta_messenger_appservice_username: molecule-metabot
matrix_bridge_mautrix_meta_messenger_bridge_command_prefix: "!molecule-meta"
matrix_bridge_mautrix_meta_messenger_bridge_displayname_suffix: "(Molecule)"
matrix_bridge_mautrix_meta_messenger_logging_min_level: debug

# The bridge's HTTP API exposure. Traefik is not deployed here, so
# nothing routes to it; what is being tested is that the role turns
# these three variables into both the container's Traefik labels and the
# Traefik is not deployed here, so nothing routes to it. What is tested is that the
# role turns these three variables into both the container's Traefik labels and the
# `appservice.public_address` the bridge itself reads.
matrix_bridge_mautrix_meta_messenger_exposure_enabled: true
matrix_bridge_mautrix_meta_messenger_exposure_hostname: bridges.molecule.local
matrix_bridge_mautrix_meta_messenger_exposure_path_prefix: /bridges/meta-messenger
matrix_bridge_mautrix_meta_messenger_scheme: https

# verify.yml runs as its own play, where role defaults are out of scope,
# so the paths it reads are pinned here as literals matching what the
# role derives from matrix_base_data_path.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_bridge_mautrix_meta_messenger_base_path: /matrix/mautrix-meta-messenger
matrix_bridge_mautrix_meta_messenger_config_path: /matrix/mautrix-meta-messenger/config
matrix_bridge_mautrix_meta_messenger_data_path: /matrix/mautrix-meta-messenger/data


+ 5
- 8
roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -72,11 +71,9 @@
- mautrix_meta_messenger_molecule_network.rc != 0
- "'already exists' not in mautrix_meta_messenger_molecule_network.stderr"

# The bridge contacts the homeserver as it starts, and calls /whoami before
# it will run at all - it exits if the id it gets back is not the bot user
# it was configured as. It is not being asked to bridge anything; there is
# no Meta account anywhere in this scenario and there is deliberately never
# going to be one. See molecule-shared/homeserver-stub.py.
# The bridge calls /whoami before it will run at all, exiting if the id it gets back is
# not the bot user it was configured as. It is not being asked to bridge anything: there
# is no Meta account in this scenario, and deliberately never will be.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 42
- 57
roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml Voir le fichier

@@ -3,29 +3,24 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: the bridge starts, reads the configuration and registration
# the role rendered, opens its appservice port, and is the Messenger-side image
# at the version the role pins.
# Proves the bridge starts, reads the configuration and registration the role rendered, opens
# its appservice port, and is the Messenger-side image at the version the role pins.
#
# The extra thing worth proving for this role in particular is that
# `matrix_bridge_mautrix_meta_messenger_meta_mode` reaches every place it feeds:
# one upstream codebase serves several Meta networks, and the mode is what picks
# which one. The scenario runs the bridge in `facebook-tor` mode rather than the
# role's default `messenger`, so the assertions below can tell the two apart.
# The extra thing worth proving here is that `..._meta_mode` reaches every place it feeds: one
# upstream codebase serves several Meta networks, and the mode picks which. The scenario runs
# `facebook-tor` rather than the role's default, so the assertions can tell the two apart.
#
# It does NOT bridge anything - there is no Facebook or Messenger account on the
# other side and there is deliberately never going to be one. See
# docs/molecule-testing.md.
# It does NOT bridge anything: no Facebook or Messenger account is on the other side, and
# deliberately never will be. See docs/molecule-testing.md.
- name: Verify mautrix-meta-messenger
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"
# Lazily evaluated, so they are only resolved by the tasks that come after the
# matching slurp. Deliberately not set_fact: the rendered configuration
# contains the bridge's own Go templates (`{{.}}` in `username_template`), and
# a stored fact gets templated again on every lookup, which would try to
# Lazily evaluated, so they resolve only in the tasks after the matching slurp.
# Deliberately not set_fact: the rendered configuration contains the bridge's own Go
# templates, and a stored fact gets templated again on every lookup, which would try to
# evaluate those as Jinja.
vars:
mautrix_meta_messenger_config: "{{ mautrix_meta_messenger_config_file.content | b64decode | from_yaml }}"
@@ -34,9 +29,8 @@
gather_facts: false

tasks:
# Read from the role's own defaults rather than pinned in molecule.yml, so
# the version assertion below compares the running image against what the
# role ships instead of against the scenario itself.
# From the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what the role ships, not the scenario.
- name: Load the role's defaults under a separate name
ansible.builtin.include_vars:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
@@ -51,10 +45,9 @@
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. It is
# asserted `is defined` because `| int` turns a missing property into 0 and
# would pass vacuously.
# `Restart=always` means a bridge crash-looping on unreadable config still reports
# `active`, so the restart counter is checked too. Asserted `is defined` because
# `| int` turns a missing property into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -68,10 +61,9 @@
automatic restart(s)
success_msg: "matrix-mautrix-meta-messenger.service is active and has not restarted"

# The appservice port is the bridge's own listener, the one a homeserver
# would push transactions to. It opening at all means the bridge got through
# reading its configuration, through its /whoami check against the
# homeserver, and through setting itself up.
# The appservice listener is where a homeserver would push transactions. It opening at all
# means the bridge got through reading its configuration, through its /whoami check, and
# through setting itself up.
- name: Wait for the bridge to open its appservice port
ansible.builtin.command:
argv:
@@ -107,9 +99,8 @@
src: "{{ matrix_bridge_mautrix_meta_messenger_config_path }}/config.yaml"
register: mautrix_meta_messenger_config_file

# Each of these differs from what the bridge would use on its own, so their
# presence means the role's configuration is what the bridge is running on
# rather than something that happened to agree with it.
# Each differs from what the bridge would use on its own, so their presence rules out
# a coincidence.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
@@ -120,21 +111,19 @@
- mautrix_meta_messenger_config.appservice.hs_token == matrix_bridge_mautrix_meta_messenger_homeserver_token
- mautrix_meta_messenger_config.bridge.command_prefix == matrix_bridge_mautrix_meta_messenger_bridge_command_prefix
- mautrix_meta_messenger_config.logging.min_level == matrix_bridge_mautrix_meta_messenger_logging_min_level
# matrix_admin is empty in the shared context, so the only per-domain
# permission left is the one the role derives from the homeserver domain.
# matrix_admin is empty in the shared context, so the only per-domain permission
# left is the one the role derives from the homeserver domain.
- mautrix_meta_messenger_config.bridge.permissions[matrix_bridge_mautrix_meta_messenger_homeserver_domain] == 'user'
# From the shared context rather than from this scenario, but the role
# is what has to carry it into the configuration.
# From the shared context rather than this scenario, but the role is what has to
# carry it into the configuration.
- mautrix_meta_messenger_config.encryption.allow == matrix_bridges_encryption_enabled
fail_msg: "The rendered configuration does not carry the scenario's values"
success_msg: "The rendered configuration carries the scenario's values"

# The mode is the variable that makes this role family unusual, and it is
# not written into the configuration as-is: the role expands it into an
# appservice id, a ghost username prefix, a bot displayname and the bridge's
# `tor` switch. Each of these holds a different value under the role's
# default `messenger` mode, so together they are what proves the choice
# propagated rather than being ignored.
# The mode is not written into the configuration as-is: the role expands it into an
# appservice id, a ghost username prefix, a bot displayname and the bridge's `tor` switch.
# Each holds a different value under the role's default mode, so together they prove the
# choice propagated rather than being ignored.
- name: Assert the configuration reflects the Meta mode the scenario selected
ansible.builtin.assert:
that:
@@ -173,9 +162,8 @@
{{ mautrix_meta_messenger_config.appservice.public_address | default('unset') }}
success_msg: "The rendered configuration carries the public address the exposure settings imply"

# The registration file is the half of the appservice handshake the
# homeserver reads, and it is generated by the role rather than by the
# bridge, so it is worth checking on its own.
# The role generates the registration; the bridge only consumes it. Worth checking on its
# own, as it is the half of the handshake the homeserver reads.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_mautrix_meta_messenger_config_path }}/registration.yaml"
@@ -192,10 +180,9 @@
fail_msg: "The appservice registration does not carry the scenario's tokens, id and bot user"
success_msg: "The appservice registration carries the scenario's tokens, id and bot user"

# The namespaces are regexes the role assembles, so rather than comparing
# them as strings - which would only re-derive the role's own escaping -
# they are checked by what they do: cover the bot, cover this mode's ghosts,
# and not cover the ghosts of the mode the role would have defaulted to.
# The namespaces are regexes the role assembles. Comparing them as strings would only
# re-derive the role's own escaping, so they are checked by what they match: the bot,
# this mode's ghosts, and not the ghosts of the mode the role would have defaulted to.
- name: Assert the registration namespaces cover the bot and this mode's ghost users
ansible.builtin.assert:
that:
@@ -214,8 +201,8 @@
mautrix_meta_messenger_messenger_ghost_mxid: "@messenger_1234567890:{{ matrix_bridge_mautrix_meta_messenger_homeserver_domain }}"
mautrix_meta_messenger_bot_mxid: "@{{ matrix_bridge_mautrix_meta_messenger_appservice_username }}:{{ matrix_bridge_mautrix_meta_messenger_homeserver_domain }}"

# The cheap proof that the data path reached the process and is writable by
# the uid the role runs the container as.
# Cheap proof that the data path reached the process and is writable by the uid the role
# runs the container as.
- name: Look for the bridge's sqlite database under the role's data path
ansible.builtin.stat:
path: "{{ matrix_bridge_mautrix_meta_messenger_data_path }}/mautrix-meta.db"
@@ -244,10 +231,9 @@
register: mautrix_meta_messenger_image
changed_when: false

# Both Meta bridges are published to the same image repository, with
# Instagram's tags carrying an `ig-` prefix, so the tag is compared whole
# rather than by substring: an `ig-` prefix would mean this role pulled the
# other bridge's image.
# Both Meta bridges publish to the same image repository, with Instagram's tags carrying
# an `ig-` prefix. Compared whole rather than by substring, so pulling the other bridge's
# image would fail here.
- name: Assert the running container is the Messenger image at the version defaults/main.yml pins
ansible.builtin.assert:
that:
@@ -263,11 +249,10 @@
src: "{{ matrix_bridge_mautrix_meta_messenger_base_path }}/labels"
register: mautrix_meta_messenger_labels

# Traefik is not running here, so what is checked is what the role wrote,
# not what a reverse-proxy would do with it. The port matters: it is
# hardcoded in the role's templates rather than derived from a variable, so
# nothing else in this scenario would catch it drifting from the port the
# bridge actually listens on.
# Traefik is not running here, so this checks what the role wrote, not what a reverse
# proxy would do with it. The port matters: it is hardcoded in the role's templates rather
# than derived from a variable, so nothing else here would catch it drifting from the port
# the bridge actually listens on.
- name: Assert the labels route the exposure hostname and path prefix to the appservice port
ansible.builtin.assert:
that:


+ 10
- 16
roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml Voir le fichier

@@ -29,35 +29,29 @@ provisioner:
all:
matrix_bridge_mautrix_whatsapp_container_network: mautrix-whatsapp-molecule

# The homeserver stub prepare.yml stands up. The bridge contacts it
# while starting; it is not a real homeserver and nothing is asserted
# The stub prepare.yml stands up. Not a real homeserver, and nothing is asserted
# about it.
matrix_bridge_mautrix_whatsapp_homeserver_address: http://matrix.molecule.local:8008

# sqlite keeps the scenario to one container. The role only requires a
# database hostname when the engine is postgres, and testing which
# database engine the bridge can talk to is not what this proves.
# sqlite keeps the scenario to one container. Which database engine the bridge can
# talk to is not what this proves.
matrix_bridge_mautrix_whatsapp_database_engine: sqlite

# Appservice tokens. These are what the bridge and homeserver would
# authenticate to each other with; here they only have to reach the
# rendered configuration and the registration file.
# Here these only have to reach the rendered configuration and the registration.
matrix_bridge_mautrix_whatsapp_appservice_token: molecule_as_token_4f2a91
matrix_bridge_mautrix_whatsapp_homeserver_token: molecule_hs_token_9b3e77

# Deliberately different from the role's defaults, so verify.yml can
# tell what the role rendered apart from what the bridge would have
# defaulted to on its own.
# Different from the role's defaults, so verify.yml can tell what the role rendered
# apart from what the bridge would have chosen.
matrix_bridge_mautrix_whatsapp_appservice_bot_username: molecule-whatsappbot
matrix_bridge_mautrix_whatsapp_homeserver_domain: molecule.local

# Traefik is not deployed here, so the labels the role would render for
# it are switched off and their absence is asserted instead.
# Traefik is not deployed here, so the labels the role would render for it are
# switched off and their absence is asserted instead.
matrix_bridge_mautrix_whatsapp_container_labels_traefik_enabled: false

# verify.yml runs as its own play, where role defaults are out of scope,
# so the paths it reads are pinned here as literals matching what the
# role derives from matrix_base_data_path.
# verify.yml runs as its own play, where the role's defaults are out of scope,
# so the paths it reads are pinned here to match what the role derives.
matrix_bridge_mautrix_whatsapp_base_path: /matrix/mautrix-whatsapp
matrix_bridge_mautrix_whatsapp_config_path: /matrix/mautrix-whatsapp/config
matrix_bridge_mautrix_whatsapp_data_path: /matrix/mautrix-whatsapp/data


+ 4
- 5
roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml Voir le fichier

@@ -31,9 +31,8 @@
docker_daemon_options:
storage-driver: fuse-overlayfs

# The role's file tasks set owner/group by name and Ansible resolves those
# through the passwd database, so they have to exist first. matrix-base
# creates them in a real deployment.
# The role's file tasks set owner/group by name, which Ansible resolves through the
# passwd database, so they have to exist first. `matrix-base` creates them for real.
- name: Ensure the matrix group exists
ansible.builtin.group:
name: "{{ matrix_group_name }}"
@@ -70,8 +69,8 @@
- mautrix_whatsapp_molecule_network.rc != 0
- "'already exists' not in mautrix_whatsapp_molecule_network.stderr"

# The bridge contacts the homeserver as it starts. It is not being asked to
# bridge anything - see molecule-shared/homeserver-stub.py.
# The bridge contacts the homeserver as it starts. It is not being asked to bridge
# anything. See molecule-shared/homeserver-stub.py.
- name: Ensure the homeserver stub is running
ansible.builtin.include_tasks:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml"


+ 18
- 23
roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml Voir le fichier

@@ -3,10 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# What this proves: the bridge starts, reads the configuration and registration
# the role rendered, opens its appservice port, and is the version the role
# pins. It does NOT bridge anything - there is no WhatsApp on the other side and
# there is deliberately never going to be one. See docs/molecule-testing.md.
# Proves the bridge starts, reads the configuration and registration the role rendered, opens
# its appservice port, and is the version the role pins.
#
# It does NOT bridge anything: there is no WhatsApp on the other side, and deliberately never
# will be. See docs/molecule-testing.md.
- name: Verify mautrix-whatsapp
hosts: all
become: true
@@ -16,9 +17,8 @@
gather_facts: false

tasks:
# Read from the role's own defaults rather than pinned in molecule.yml, so
# the version assertion below compares the running image against what the
# role ships instead of against the scenario itself.
# From the role's own defaults rather than pinned in molecule.yml, so the version
# assertion compares the running image against what the role ships, not the scenario.
- name: Load the role's defaults under a separate name
ansible.builtin.include_vars:
file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
@@ -33,10 +33,9 @@
delay: 5
failed_when: false

# `Restart=always` means a bridge crash-looping on a configuration it cannot
# read still reports `active`, so the restart counter is checked too. It is
# asserted `is defined` because `| int` turns a missing property into 0 and
# would pass vacuously.
# `Restart=always` means a bridge crash-looping on unreadable config still reports
# `active`, so the restart counter is checked too. Asserted `is defined` because
# `| int` turns a missing property into 0 and would pass vacuously.
- name: Assert the service is active and has not been restarting
ansible.builtin.assert:
that:
@@ -50,9 +49,8 @@
automatic restart(s)
success_msg: "matrix-mautrix-whatsapp.service is active and has not restarted"

# The appservice port is the bridge's own listener, the one a homeserver
# would push transactions to. It opening at all means the bridge got through
# reading its configuration and setting itself up.
# The appservice listener is where a homeserver would push transactions. It opening at all
# means the bridge got through reading its configuration and setting itself up.
- name: Wait for the bridge to open its appservice port
ansible.builtin.command:
argv:
@@ -88,9 +86,8 @@
src: "{{ matrix_bridge_mautrix_whatsapp_config_path }}/config.yaml"
register: mautrix_whatsapp_config_file

# Each of these differs from what the bridge would use on its own, so their
# presence means the role's configuration is what the bridge is running on
# rather than something that happened to agree with it.
# Each differs from what the bridge would use on its own, so their presence rules out
# a coincidence.
- name: Assert the rendered configuration carries this scenario's values
ansible.builtin.assert:
that:
@@ -102,9 +99,8 @@
vars:
mautrix_whatsapp_config_rendered: "{{ mautrix_whatsapp_config_file.content | b64decode }}"

# The registration file is the half of the appservice handshake the
# homeserver reads, and it is generated by the role rather than by the
# bridge, so it is worth checking on its own.
# The role generates the registration; the bridge only consumes it. Worth checking on its
# own, as it is the half of the handshake the homeserver reads.
- name: Read the appservice registration the role rendered
ansible.builtin.slurp:
src: "{{ matrix_bridge_mautrix_whatsapp_config_path }}/registration.yaml"
@@ -121,9 +117,8 @@
vars:
mautrix_whatsapp_registration_rendered: "{{ mautrix_whatsapp_registration_file.content | b64decode }}"

# sqlite was chosen in molecule.yml, so the bridge should have created its
# database under the role's data path. This is the cheap proof that the data
# path reached the process and is writable by the uid the role runs it as.
# Cheap proof that the data path reached the process and is writable by the uid the role
# runs it as.
- name: Look for the bridge's sqlite database under the role's data path
ansible.builtin.stat:
path: "{{ matrix_bridge_mautrix_whatsapp_data_path }}/mautrix-whatsapp.db"


Chargement…
Annuler
Enregistrer