From 0ae18e7a1641163c28304e085821f4daa00db55b Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Thu, 27 Aug 2026 16:24:39 +0300 Subject: [PATCH] Add a Molecule scenario for matrix-reminder-bot A bot rather than a bridge, and not an appservice: it logs into the homeserver as an ordinary user with a password, and keeps its reminders in a local SQLite database. That makes it a cheap second data point for the bot shape, and it is closer to matrix-alertmanager-receiver than to the bridges - except that it has no HTTP surface at all, so there is nothing to probe. What the scenario proves instead: - The unit is active and has not restarted. The bot parses its config file before its own catch-all retry loop starts, so anything wrong in what the role rendered surfaces as a crash loop rather than as a running process. - The bot reached "Logged in as @molecule.reminder-bot:molecule.local" in the journal. That line is only reached once the login call came back as something other than an error, so it covers the homeserver URL, the user ID and the password the role rendered in one go - a real login round-trip against the shared stub, which already answers /_matrix/client/v3/login with an access token. No stub changes were needed. - The SQLite database landed at the path the role configured, owned by the role's uid, with the role's own default name (bot.db) absent as a negative control - so the storage configuration reached the running process and not just the file on disk. - matrix-nio populated its encryption store under the role's data path, inside an otherwise read-only container. - The container runs as the playbook context's uid:gid with the configured timezone on TZ, and carries the version defaults/main.yml pins. - `..._configuration_extension_yaml` was merged over the role's template: device_name is hardcoded in the template, so overriding it is only possible through the extension. Every value the scenario sets differs from both the role's defaults and the bot's own fallbacks - localpart, command prefix, timezone, database filename, both the allowlist and the blocklist. Falsified by pointing the homeserver URL at a dead port. The service stayed `active` with NRestarts == 0 and that assertion passed, because the bot catches every exception and retries every 15s rather than exiting - a good illustration of why `active` on its own proves nothing here. The run failed at "Assert the bot logged in as the user the role configured", which is the assertion carrying the weight. Surprise worth recording: the journal is read through a grep rather than a `--lines=N` tail. The startup lines are the oldest in the journal, and if the stub ever answers /sync instantly the bot's sync loop spins fast enough to bury them under thousands of lines within a minute. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT --- .../molecule/default/converge.yml | 43 ++++ .../molecule/default/molecule.yml | 98 +++++++ .../molecule/default/prepare.yml | 85 ++++++ .../molecule/default/requirements.yml | 1 + .../molecule/default/verify.yml | 243 ++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml create mode 100644 roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml create mode 100644 roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml create mode 120000 roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/requirements.yml create mode 100644 roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml 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 new file mode 100644 index 000000000..554ff5a27 --- /dev/null +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/converge.yml @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2026 Slavi Pantaleev +# +# 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. +- name: Include roles for matrix-reminder-bot Molecule tests + hosts: all + become: true + vars_files: + - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml" + gather_facts: true + tasks: + - name: Include roles for matrix-reminder-bot Molecule tests + ansible.builtin.include_role: + name: "{{ role_name }}" + public: true + loop: + - com.devture.ansible.role.playbook_help + - com.devture.ansible.role.systemd_docker_base + - "custom/{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" + loop_control: + loop_var: role_name + +# 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. +- name: Ensure matrix-reminder-bot is started + hosts: all + become: true + gather_facts: false + tasks: + - name: Ensure systemd daemon is reloaded + ansible.builtin.systemd_service: + daemon_reload: true + + - name: Ensure matrix-bot-matrix-reminder-bot systemd service is started + ansible.builtin.systemd_service: + name: matrix-bot-matrix-reminder-bot.service + state: started 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 new file mode 100644 index 000000000..d05e50785 --- /dev/null +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/molecule.yml @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2026 Slavi Pantaleev +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml + force: true +driver: + name: docker +platforms: + - name: matrix-reminder-bot-${MOLECULE_DISTRO:-ubuntu2604}-default + image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2604}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + config_options: + defaults: + callback_result_format: yaml + inventory: + group_vars: + all: + matrix_bot_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. + 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 + # 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. + 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. + matrix_bot_matrix_reminder_bot_allowlist_enabled: true + matrix_bot_matrix_reminder_bot_allowlist_regexes_custom: + - "@molecule-allowed:molecule.local" + matrix_bot_matrix_reminder_bot_blocklist_enabled: true + 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. + 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. + 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`. + 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 + matrix_bot_matrix_reminder_bot_data_store_path: /matrix/matrix-reminder-bot/data/store + env: + # Workaround for https://github.com/ansible/molecule/issues/4391 + ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/../..:/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:~/.ansible/roles +scenario: + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy +verifier: + name: ansible 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 new file mode 100644 index 000000000..737dec095 --- /dev/null +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/prepare.yml @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: 2026 Slavi Pantaleev +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +--- +- name: Prepare matrix-reminder-bot Molecule tests + hosts: all + become: true + vars_files: + - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml" + - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml" + gather_facts: true + tasks: + - name: Ensure apt cache is updated + ansible.builtin.apt: + update_cache: true + cache_valid_time: 600 + when: ansible_os_family == 'Debian' + + - name: Ensure required packages are installed + ansible.builtin.package: + name: + - python3-requests + - fuse-overlayfs + state: present + + - name: Ensure Docker is installed + ansible.builtin.include_role: + name: ansible-role-docker + vars: + docker_daemon_options: + storage-driver: fuse-overlayfs + + # 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. + - name: Ensure the matrix group exists + ansible.builtin.group: + name: "{{ matrix_group_name }}" + gid: "{{ matrix_user_gid }}" + state: present + + - name: Ensure the matrix user exists + ansible.builtin.user: + name: "{{ matrix_user_name }}" + uid: "{{ matrix_user_uid }}" + group: "{{ matrix_group_name }}" + create_home: false + system: true + state: present + + - name: Ensure the base data path exists + ansible.builtin.file: + path: "{{ matrix_base_data_path }}" + state: directory + owner: "{{ matrix_user_name }}" + group: "{{ matrix_group_name }}" + mode: "0750" + + # 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. + - name: Ensure the container network the role attaches to exists + ansible.builtin.command: + argv: + - docker + - network + - create + - "{{ matrix_bot_matrix_reminder_bot_container_network }}" + register: matrix_bot_matrix_reminder_bot_molecule_network + changed_when: matrix_bot_matrix_reminder_bot_molecule_network.rc == 0 + failed_when: + - 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. + - name: Ensure the homeserver stub is running + ansible.builtin.include_tasks: + file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/tasks/homeserver-stub.yml" + vars: + molecule_shared_stub_network: "{{ matrix_bot_matrix_reminder_bot_container_network }}" + molecule_shared_stub_user_id: "@{{ matrix_bot_matrix_reminder_bot_matrix_user_id_localpart }}:{{ matrix_domain }}" diff --git a/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/requirements.yml b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/requirements.yml new file mode 120000 index 000000000..1567a5392 --- /dev/null +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/requirements.yml @@ -0,0 +1 @@ +../../../../../molecule-shared/requirements.yml \ No newline at end of file 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 new file mode 100644 index 000000000..56750b957 --- /dev/null +++ b/roles/custom/matrix-bot-matrix-reminder-bot/molecule/default/verify.yml @@ -0,0 +1,243 @@ +# SPDX-FileCopyrightText: 2026 Slavi Pantaleev +# +# 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. +# +# 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. +- name: Verify matrix-reminder-bot + hosts: all + become: true + vars_files: + - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml" + - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml" + gather_facts: false + + vars: + matrix_bot_matrix_reminder_bot_molecule_user_id: "@{{ matrix_bot_matrix_reminder_bot_matrix_user_id_localpart }}:{{ matrix_domain }}" + 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. + - name: Load the role's defaults under a separate name + ansible.builtin.include_vars: + file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml" + name: matrix_bot_matrix_reminder_bot_role_defaults + + - name: Wait for the matrix-reminder-bot service to become active + ansible.builtin.systemd_service: + name: matrix-bot-matrix-reminder-bot.service + register: matrix_bot_matrix_reminder_bot_service + until: matrix_bot_matrix_reminder_bot_service.status.ActiveState == 'active' + retries: 30 + 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. + - name: Assert the service is active and has not been restarting + ansible.builtin.assert: + that: + - matrix_bot_matrix_reminder_bot_service.status.ActiveState == 'active' + - matrix_bot_matrix_reminder_bot_service.status.NRestarts is defined + - matrix_bot_matrix_reminder_bot_service.status.NRestarts | int == 0 + fail_msg: >- + matrix-bot-matrix-reminder-bot.service is + {{ matrix_bot_matrix_reminder_bot_service.status.ActiveState | default('unknown') }} + after {{ matrix_bot_matrix_reminder_bot_service.status.NRestarts | default('?') }} + 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. + # + # 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 + # "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: + cmd: >- + set -o pipefail; + journalctl --unit=matrix-bot-matrix-reminder-bot.service --no-pager --output=cat --lines=all + | grep -E 'Logged in as|Startup complete|Failed to login|Database initialization' | head -n 50 || true + executable: /bin/bash + register: matrix_bot_matrix_reminder_bot_journal + changed_when: false + until: "'Startup complete' in matrix_bot_matrix_reminder_bot_journal.stdout" + retries: 24 + 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. + - name: Assert the bot logged in as the user the role configured + ansible.builtin.assert: + that: + - "'Startup complete' in matrix_bot_matrix_reminder_bot_journal.stdout" + - "'Logged in as ' + matrix_bot_matrix_reminder_bot_molecule_user_id in matrix_bot_matrix_reminder_bot_journal.stdout" + - "'Failed to login' not in matrix_bot_matrix_reminder_bot_journal.stdout" + fail_msg: >- + The bot did not log in as {{ matrix_bot_matrix_reminder_bot_molecule_user_id }} + and reach startup + 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. + - name: Assert the bot opened the database engine the role selected + ansible.builtin.assert: + that: + - "\"Database initialization of type 'sqlite' complete\" in matrix_bot_matrix_reminder_bot_journal.stdout" + fail_msg: "The bot did not report a completed SQLite database initialization" + success_msg: "The bot initialized the SQLite database the role pointed it at" + + - name: Read the configuration file the role rendered + ansible.builtin.slurp: + 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. + - name: Assert the rendered configuration carries this scenario's values + ansible.builtin.assert: + that: + - matrix_bot_matrix_reminder_bot_molecule_user_id in matrix_bot_matrix_reminder_bot_config_rendered + - matrix_bot_matrix_reminder_bot_matrix_user_password in matrix_bot_matrix_reminder_bot_config_rendered + - matrix_bot_matrix_reminder_bot_matrix_homeserver_url in matrix_bot_matrix_reminder_bot_config_rendered + - matrix_bot_matrix_reminder_bot_reminders_timezone in matrix_bot_matrix_reminder_bot_config_rendered + - matrix_bot_matrix_reminder_bot_command_prefix in matrix_bot_matrix_reminder_bot_config_rendered + - "'sqlite://' + matrix_bot_matrix_reminder_bot_sqlite_database_path_in_container in matrix_bot_matrix_reminder_bot_config_rendered" + - "'@molecule-allowed:molecule.local' in matrix_bot_matrix_reminder_bot_config_rendered" + - "'.*:blocked.molecule.local' in matrix_bot_matrix_reminder_bot_config_rendered" + fail_msg: "The rendered configuration does not carry the scenario's settings" + success_msg: "The rendered configuration carries the scenario's settings" + 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. + - name: Assert the configuration extension was merged over the template + ansible.builtin.assert: + that: + - "'device_name: Molecule Reminder Bot' in matrix_bot_matrix_reminder_bot_config_rendered" + - "'device_name: Reminder Bot' not in matrix_bot_matrix_reminder_bot_config_rendered" + fail_msg: >- + The configuration extension did not override the device name the + role's template hardcodes + success_msg: "The configuration extension was merged over the role's template" + 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. + - name: Stat the database at the path the scenario configured + ansible.builtin.stat: + path: "{{ matrix_bot_matrix_reminder_bot_sqlite_database_path_local }}" + register: matrix_bot_matrix_reminder_bot_database + + - name: Assert the database landed under the role's data path, owned by the role's user + ansible.builtin.assert: + that: + - matrix_bot_matrix_reminder_bot_database.stat.exists + - matrix_bot_matrix_reminder_bot_database.stat.uid == matrix_user_uid + - matrix_bot_matrix_reminder_bot_database.stat.gid == matrix_user_gid + fail_msg: >- + {{ matrix_bot_matrix_reminder_bot_sqlite_database_path_local }} is missing or is not + owned by {{ matrix_user_uid }}:{{ matrix_user_gid }} + 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. + - 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" + register: matrix_bot_matrix_reminder_bot_default_database + + - name: Assert the role's default database name was not used + ansible.builtin.assert: + that: + - not matrix_bot_matrix_reminder_bot_default_database.stat.exists + fail_msg: >- + {{ matrix_bot_matrix_reminder_bot_data_path }}/bot.db exists as well, so the + database at the configured path does not prove the role's storage + 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. + - name: List the encryption store the role created + ansible.builtin.find: + paths: "{{ matrix_bot_matrix_reminder_bot_data_store_path }}" + file_type: file + register: matrix_bot_matrix_reminder_bot_store_files + + - name: Assert the bot wrote its encryption store where the role put it + ansible.builtin.assert: + that: + - matrix_bot_matrix_reminder_bot_store_files.matched | int > 0 + fail_msg: >- + {{ matrix_bot_matrix_reminder_bot_data_store_path }} is empty, so the bot never + got far enough to open its encryption store + success_msg: "The bot wrote its encryption store under the role's data path" + + - name: Read the running container's user and environment + ansible.builtin.command: + argv: + - docker + - container + - inspect + - matrix-bot-matrix-reminder-bot + - --format + - "{{ '{{' }} .Config.User {{ '}}' }} {{ '{{' }} json .Config.Env {{ '}}' }} {{ '{{' }} .Config.Image {{ '}}' }}" + 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. + - name: Assert the container runs as the role's user with the configured timezone + ansible.builtin.assert: + that: + - "'\"TZ=' + matrix_bot_matrix_reminder_bot_reminders_timezone + '\"' in matrix_bot_matrix_reminder_bot_container.stdout" + - matrix_bot_matrix_reminder_bot_molecule_container_user in matrix_bot_matrix_reminder_bot_container.stdout + fail_msg: >- + The container does not run as {{ matrix_user_uid }}:{{ matrix_user_gid }} with + TZ={{ matrix_bot_matrix_reminder_bot_reminders_timezone }} + ({{ matrix_bot_matrix_reminder_bot_container.stdout }}) + success_msg: >- + The container runs as {{ matrix_user_uid }}:{{ matrix_user_gid }} with + TZ={{ matrix_bot_matrix_reminder_bot_reminders_timezone }} + + - name: Assert the running container is the version defaults/main.yml pins + ansible.builtin.assert: + that: + - matrix_bot_matrix_reminder_bot_role_defaults.matrix_bot_matrix_reminder_bot_version | string in matrix_bot_matrix_reminder_bot_container.stdout + fail_msg: >- + The running container is {{ matrix_bot_matrix_reminder_bot_container.stdout }}, which + does not carry the pinned version + {{ matrix_bot_matrix_reminder_bot_role_defaults.matrix_bot_matrix_reminder_bot_version }} + success_msg: "The running container is the version defaults/main.yml pins"