From c447e1528b6b954d115c84509ced859a67470a55 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Thu, 27 Aug 2026 17:20:44 +0300 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT --- molecule-shared/homeserver-stub.py | 60 +++++------ molecule-shared/playbook-context.yml | 46 ++++----- molecule-shared/requirements.yml | 4 +- molecule-shared/tasks/homeserver-stub.yml | 25 +++-- molecule-shared/vars.yml | 14 ++- .../molecule/default/converge.yml | 12 +-- .../molecule/default/molecule.yml | 19 ++-- .../molecule/default/prepare.yml | 12 +-- .../molecule/default/verify.yml | 30 +++--- .../molecule/default/converge.yml | 12 +-- .../molecule/default/molecule.yml | 33 +++---- .../molecule/default/prepare.yml | 15 ++- .../molecule/default/verify.yml | 91 +++++++---------- .../molecule/default/converge.yml | 12 +-- .../molecule/default/molecule.yml | 34 +++---- .../molecule/default/prepare.yml | 17 ++-- .../molecule/default/verify.yml | 82 +++++++-------- .../molecule/default/molecule.yml | 24 +++-- .../molecule/default/prepare.yml | 9 +- .../molecule/default/verify.yml | 24 ++--- .../molecule/default/molecule.yml | 60 +++++------ .../molecule/default/prepare.yml | 5 +- .../molecule/default/verify.yml | 94 ++++++++---------- .../molecule/default/molecule.yml | 44 ++++----- .../molecule/default/prepare.yml | 10 +- .../molecule/default/verify.yml | 70 ++++++------- .../molecule/default/molecule.yml | 57 ++++------- .../molecule/default/prepare.yml | 13 +-- .../molecule/default/verify.yml | 99 ++++++++----------- .../molecule/default/molecule.yml | 26 ++--- .../molecule/default/prepare.yml | 9 +- .../molecule/default/verify.yml | 41 ++++---- 32 files changed, 460 insertions(+), 643 deletions(-) diff --git a/molecule-shared/homeserver-stub.py b/molecule-shared/homeserver-stub.py index 3331cd247..635339191 100644 --- a/molecule-shared/homeserver-stub.py +++ b/molecule-shared/homeserver-stub.py @@ -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") diff --git a/molecule-shared/playbook-context.yml b/molecule-shared/playbook-context.yml index 18f4d874c..04ab956a9 100644 --- a/molecule-shared/playbook-context.yml +++ b/molecule-shared/playbook-context.yml @@ -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 diff --git a/molecule-shared/requirements.yml b/molecule-shared/requirements.yml index 0c62300e1..4e721acca 100644 --- a/molecule-shared/requirements.yml +++ b/molecule-shared/requirements.yml @@ -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 diff --git a/molecule-shared/tasks/homeserver-stub.yml b/molecule-shared/tasks/homeserver-stub.yml index 939ff286e..118d3f744 100644 --- a/molecule-shared/tasks/homeserver-stub.yml +++ b/molecule-shared/tasks/homeserver-stub.yml @@ -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 }}" diff --git a/molecule-shared/vars.yml b/molecule-shared/vars.yml index 2a6219b17..adbfe01ea 100644 --- a/molecule-shared/vars.yml +++ b/molecule-shared/vars.yml @@ -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" diff --git a/roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml b/roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml index 4878f7551..0a68b9c97 100644 --- a/roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml +++ b/roles/custom/matrix-alertmanager-receiver/molecule/default/converge.yml @@ -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 diff --git a/roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml b/roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml index bf458c56c..27a17a5b4 100644 --- a/roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml +++ b/roles/custom/matrix-alertmanager-receiver/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml b/roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml index 50e06d4a7..d3f8a9992 100644 --- a/roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml +++ b/roles/custom/matrix-alertmanager-receiver/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml b/roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml index 1d61462ac..0fcc6718f 100644 --- a/roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml +++ b/roles/custom/matrix-alertmanager-receiver/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bot-baibot/molecule/default/converge.yml b/roles/custom/matrix-bot-baibot/molecule/default/converge.yml index 195105498..fe6a6e238 100644 --- a/roles/custom/matrix-bot-baibot/molecule/default/converge.yml +++ b/roles/custom/matrix-bot-baibot/molecule/default/converge.yml @@ -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 diff --git a/roles/custom/matrix-bot-baibot/molecule/default/molecule.yml b/roles/custom/matrix-bot-baibot/molecule/default/molecule.yml index 11f4bf7b8..5425ab70d 100644 --- a/roles/custom/matrix-bot-baibot/molecule/default/molecule.yml +++ b/roles/custom/matrix-bot-baibot/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bot-baibot/molecule/default/prepare.yml b/roles/custom/matrix-bot-baibot/molecule/default/prepare.yml index f7731e2dc..949a0e7d7 100644 --- a/roles/custom/matrix-bot-baibot/molecule/default/prepare.yml +++ b/roles/custom/matrix-bot-baibot/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bot-baibot/molecule/default/verify.yml b/roles/custom/matrix-bot-baibot/molecule/default/verify.yml index 846ff049a..00a68a6af 100644 --- a/roles/custom/matrix-bot-baibot/molecule/default/verify.yml +++ b/roles/custom/matrix-bot-baibot/molecule/default/verify.yml @@ -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" diff --git a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml index 554ff5a27..f309aa8d2 100644 --- a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml @@ -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 diff --git a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml index f8c030ee8..dbc71bd6a 100644 --- a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml index 737dec095..2bd74f3de 100644 --- a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml index 56750b957..0c59295dc 100644 --- a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml b/roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml index 249c6002c..cdf297b7d 100644 --- a/roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml +++ b/roles/custom/matrix-bridge-heisenbridge/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml b/roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml index 1058fec61..00a3f3f67 100644 --- a/roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml +++ b/roles/custom/matrix-bridge-heisenbridge/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml b/roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml index 9c1d1e4c1..692d61e01 100644 --- a/roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml +++ b/roles/custom/matrix-bridge-heisenbridge/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml b/roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml index 6e027fefc..5242dd93f 100644 --- a/roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml +++ b/roles/custom/matrix-bridge-hookshot/molecule/default/molecule.yml @@ -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: diff --git a/roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml b/roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml index 4c38489ca..975f7312a 100644 --- a/roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml +++ b/roles/custom/matrix-bridge-hookshot/molecule/default/prepare.yml @@ -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 }}" diff --git a/roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml b/roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml index bb3a3f774..5cb8694e8 100644 --- a/roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml +++ b/roles/custom/matrix-bridge-hookshot/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml index f7478fbb9..d56a55e42 100644 --- a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml +++ b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml index 1242940e0..f75776aac 100644 --- a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml +++ b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml index 83b1779d8..f8e943cd3 100644 --- a/roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml +++ b/roles/custom/matrix-bridge-mautrix-discord/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml index fc707df5d..89ea8d30e 100644 --- a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml +++ b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml index c50c68e5c..fcddabafc 100644 --- a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml +++ b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml index c7c01cf57..9ebe4487e 100644 --- a/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml +++ b/roles/custom/matrix-bridge-mautrix-meta-messenger/molecule/default/verify.yml @@ -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: diff --git a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml index ecf33de90..54f13d436 100644 --- a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml +++ b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/molecule.yml @@ -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 diff --git a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml index fa6d46020..69920322c 100644 --- a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml +++ b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/prepare.yml @@ -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" diff --git a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml index 7f217de7f..c72f2a292 100644 --- a/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml +++ b/roles/custom/matrix-bridge-mautrix-whatsapp/molecule/default/verify.yml @@ -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"