Matrix Docker Ansible eploy
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

230 řádky
13 KiB

  1. # SPDX-FileCopyrightText: 2026 Slavi Pantaleev
  2. #
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. ---
  5. # Proves matrix-reminder-bot starts on the configuration the role rendered, logs in as the
  6. # user the role gave it, opens the database at the path the role gave it, and is the version
  7. # the role pins.
  8. #
  9. # The bot has no HTTP surface to probe, so the evidence is what it says about itself in the
  10. # journal plus what it left on disk. It does NOT set real reminders. See docs/molecule-testing.md.
  11. - name: Verify matrix-reminder-bot
  12. hosts: all
  13. become: true
  14. vars_files:
  15. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
  16. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
  17. gather_facts: false
  18. vars:
  19. matrix_bot_matrix_reminder_bot_molecule_user_id: "@{{ matrix_bot_matrix_reminder_bot_matrix_user_id_localpart }}:{{ matrix_domain }}"
  20. matrix_bot_matrix_reminder_bot_molecule_container_user: "{{ matrix_user_uid }}:{{ matrix_user_gid }}"
  21. tasks:
  22. # Read from the role's own defaults rather than pinned in molecule.yml, so the version
  23. # assertion compares the running image against what defaults/main.yml ships.
  24. # Pinning it here would make that assertion compare the scenario with itself.
  25. - name: Load the role's defaults under a separate name
  26. ansible.builtin.include_vars:
  27. file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
  28. name: matrix_bot_matrix_reminder_bot_role_defaults
  29. - name: Wait for the matrix-reminder-bot service to become active
  30. ansible.builtin.systemd_service:
  31. name: matrix-bot-matrix-reminder-bot.service
  32. register: matrix_bot_matrix_reminder_bot_service
  33. until: matrix_bot_matrix_reminder_bot_service.status.ActiveState == 'active'
  34. retries: 30
  35. delay: 5
  36. failed_when: false
  37. # `Restart=always` means a bot crash-looping on unreadable config still reports `active`,
  38. # so the restart counter is checked too. The config file is parsed before the bot's own
  39. # catch-all retry loop starts, so anything wrong in what the role rendered shows up here
  40. # as restarts. Asserted `is defined` because `| int` turns a missing property into 0.
  41. - name: Assert the service is active and has not been restarting
  42. ansible.builtin.assert:
  43. that:
  44. - matrix_bot_matrix_reminder_bot_service.status.ActiveState == 'active'
  45. - matrix_bot_matrix_reminder_bot_service.status.NRestarts is defined
  46. - matrix_bot_matrix_reminder_bot_service.status.NRestarts | int == 0
  47. fail_msg: >-
  48. matrix-bot-matrix-reminder-bot.service is
  49. {{ matrix_bot_matrix_reminder_bot_service.status.ActiveState | default('unknown') }}
  50. after {{ matrix_bot_matrix_reminder_bot_service.status.NRestarts | default('?') }}
  51. automatic restart(s)
  52. success_msg: "matrix-bot-matrix-reminder-bot.service is active and has not restarted"
  53. # The unit runs `docker start --attach`, so the container's output is in the journal
  54. # despite `--log-driver=none`. It is the only thing this bot reports about itself.
  55. #
  56. # Filtered rather than tailed: startup lines are the OLDEST in the journal, so a
  57. # `--lines=N` tail loses them behind anything logged later, and reading it whole pulls
  58. # unbounded text into a variable. The filter keeps the failure line too, so the
  59. # "did not fail to log in" assertion below still has something to see.
  60. - name: Wait for the bot to report that it finished starting up
  61. ansible.builtin.shell:
  62. cmd: >-
  63. set -o pipefail;
  64. journalctl --unit=matrix-bot-matrix-reminder-bot.service --no-pager --output=cat --lines=all
  65. | grep -E 'Logged in as|Startup complete|Failed to login|Database initialization' | head -n 50 || true
  66. executable: /bin/bash
  67. register: matrix_bot_matrix_reminder_bot_journal
  68. changed_when: false
  69. until: "'Startup complete' in matrix_bot_matrix_reminder_bot_journal.stdout"
  70. retries: 24
  71. delay: 5
  72. failed_when: false
  73. # "Logged in as ..." is only reached once the login call returned something other than a
  74. # LoginError, so this covers the whole chain at once: homeserver URL, user ID and password
  75. # were all good enough for a real login round-trip.
  76. - name: Assert the bot logged in as the user the role configured
  77. ansible.builtin.assert:
  78. that:
  79. - "'Startup complete' in matrix_bot_matrix_reminder_bot_journal.stdout"
  80. - "'Logged in as ' + matrix_bot_matrix_reminder_bot_molecule_user_id in matrix_bot_matrix_reminder_bot_journal.stdout"
  81. - "'Failed to login' not in matrix_bot_matrix_reminder_bot_journal.stdout"
  82. fail_msg: >-
  83. The bot did not log in as {{ matrix_bot_matrix_reminder_bot_molecule_user_id }}
  84. and reach startup
  85. success_msg: >-
  86. The bot logged in as {{ matrix_bot_matrix_reminder_bot_molecule_user_id }} and finished starting up
  87. # The role picks the storage engine by building the connection string the bot parses,
  88. # and the bot names the type it settled on once the database is open.
  89. - name: Assert the bot opened the database engine the role selected
  90. ansible.builtin.assert:
  91. that:
  92. - "\"Database initialization of type 'sqlite' complete\" in matrix_bot_matrix_reminder_bot_journal.stdout"
  93. fail_msg: "The bot did not report a completed SQLite database initialization"
  94. success_msg: "The bot initialized the SQLite database the role pointed it at"
  95. - name: Read the configuration file the role rendered
  96. ansible.builtin.slurp:
  97. src: "{{ matrix_bot_matrix_reminder_bot_config_path }}/config.yaml"
  98. register: matrix_bot_matrix_reminder_bot_config_file
  99. # Every one differs from both the role's defaults and the bot's own fallbacks, so their
  100. # presence means the role rendered this file rather than coinciding with it.
  101. - name: Assert the rendered configuration carries this scenario's values
  102. ansible.builtin.assert:
  103. that:
  104. - matrix_bot_matrix_reminder_bot_molecule_user_id in matrix_bot_matrix_reminder_bot_config_rendered
  105. - matrix_bot_matrix_reminder_bot_matrix_user_password in matrix_bot_matrix_reminder_bot_config_rendered
  106. - matrix_bot_matrix_reminder_bot_matrix_homeserver_url in matrix_bot_matrix_reminder_bot_config_rendered
  107. - matrix_bot_matrix_reminder_bot_reminders_timezone in matrix_bot_matrix_reminder_bot_config_rendered
  108. - matrix_bot_matrix_reminder_bot_command_prefix in matrix_bot_matrix_reminder_bot_config_rendered
  109. - "'sqlite://' + matrix_bot_matrix_reminder_bot_sqlite_database_path_in_container in matrix_bot_matrix_reminder_bot_config_rendered"
  110. - "'@molecule-allowed:molecule.local' in matrix_bot_matrix_reminder_bot_config_rendered"
  111. - "'.*:blocked.molecule.local' in matrix_bot_matrix_reminder_bot_config_rendered"
  112. fail_msg: "The rendered configuration does not carry the scenario's settings"
  113. success_msg: "The rendered configuration carries the scenario's settings"
  114. vars:
  115. matrix_bot_matrix_reminder_bot_config_rendered: "{{ matrix_bot_matrix_reminder_bot_config_file.content | b64decode }}"
  116. # `device_name` is hardcoded in the role's template, so this value can only be here if
  117. # `..._configuration_extension_yaml` was merged over it rather than ignored.
  118. - name: Assert the configuration extension was merged over the template
  119. ansible.builtin.assert:
  120. that:
  121. - "'device_name: Molecule Reminder Bot' in matrix_bot_matrix_reminder_bot_config_rendered"
  122. - "'device_name: Reminder Bot' not in matrix_bot_matrix_reminder_bot_config_rendered"
  123. fail_msg: >-
  124. The configuration extension did not override the device name the
  125. role's template hardcodes
  126. success_msg: "The configuration extension was merged over the role's template"
  127. vars:
  128. matrix_bot_matrix_reminder_bot_config_rendered: "{{ matrix_bot_matrix_reminder_bot_config_file.content | b64decode }}"
  129. # With no HTTP surface, where the database landed is the evidence that the storage
  130. # configuration reached the running process and not merely the file on disk.
  131. - name: Stat the database at the path the scenario configured
  132. ansible.builtin.stat:
  133. path: "{{ matrix_bot_matrix_reminder_bot_sqlite_database_path_local }}"
  134. register: matrix_bot_matrix_reminder_bot_database
  135. - name: Assert the database landed under the role's data path, owned by the role's user
  136. ansible.builtin.assert:
  137. that:
  138. - matrix_bot_matrix_reminder_bot_database.stat.exists
  139. - matrix_bot_matrix_reminder_bot_database.stat.uid == matrix_user_uid
  140. - matrix_bot_matrix_reminder_bot_database.stat.gid == matrix_user_gid
  141. fail_msg: >-
  142. {{ matrix_bot_matrix_reminder_bot_sqlite_database_path_local }} is missing or is not
  143. owned by {{ matrix_user_uid }}:{{ matrix_user_gid }}
  144. success_msg: >-
  145. The database is at the configured path, owned by {{ matrix_user_uid }}:{{ matrix_user_gid }}
  146. # Negative control for the assertion above: the role's own default database name must NOT
  147. # appear, or a file at the configured path would prove nothing.
  148. - name: Stat the database name the role would have used by default
  149. ansible.builtin.stat:
  150. path: "{{ matrix_bot_matrix_reminder_bot_data_path }}/bot.db"
  151. register: matrix_bot_matrix_reminder_bot_default_database
  152. - name: Assert the role's default database name was not used
  153. ansible.builtin.assert:
  154. that:
  155. - not matrix_bot_matrix_reminder_bot_default_database.stat.exists
  156. fail_msg: >-
  157. {{ matrix_bot_matrix_reminder_bot_data_path }}/bot.db exists as well, so the
  158. database at the configured path does not prove the role's storage
  159. configuration reached the bot
  160. success_msg: "Only the configured database path was used"
  161. # matrix-nio writes its encryption store here once login succeeds, so a populated directory
  162. # means the bot could use the store path the role created inside a read-only container.
  163. - name: List the encryption store the role created
  164. ansible.builtin.find:
  165. paths: "{{ matrix_bot_matrix_reminder_bot_data_store_path }}"
  166. file_type: file
  167. register: matrix_bot_matrix_reminder_bot_store_files
  168. - name: Assert the bot wrote its encryption store where the role put it
  169. ansible.builtin.assert:
  170. that:
  171. - matrix_bot_matrix_reminder_bot_store_files.matched | int > 0
  172. fail_msg: >-
  173. {{ matrix_bot_matrix_reminder_bot_data_store_path }} is empty, so the bot never
  174. got far enough to open its encryption store
  175. success_msg: "The bot wrote its encryption store under the role's data path"
  176. - name: Read the running container's user and environment
  177. ansible.builtin.command:
  178. argv:
  179. - docker
  180. - container
  181. - inspect
  182. - matrix-bot-matrix-reminder-bot
  183. - --format
  184. - "{{ '{{' }} .Config.User {{ '}}' }} {{ '{{' }} json .Config.Env {{ '}}' }} {{ '{{' }} .Config.Image {{ '}}' }}"
  185. register: matrix_bot_matrix_reminder_bot_container
  186. changed_when: false
  187. # The timezone reaches the container twice, through the config file checked above and
  188. # through TZ on the unit. The uid/gid come from the playbook context, not from the image.
  189. - name: Assert the container runs as the role's user with the configured timezone
  190. ansible.builtin.assert:
  191. that:
  192. - "'\"TZ=' + matrix_bot_matrix_reminder_bot_reminders_timezone + '\"' in matrix_bot_matrix_reminder_bot_container.stdout"
  193. - matrix_bot_matrix_reminder_bot_molecule_container_user in matrix_bot_matrix_reminder_bot_container.stdout
  194. fail_msg: >-
  195. The container does not run as {{ matrix_user_uid }}:{{ matrix_user_gid }} with
  196. TZ={{ matrix_bot_matrix_reminder_bot_reminders_timezone }}
  197. ({{ matrix_bot_matrix_reminder_bot_container.stdout }})
  198. success_msg: >-
  199. The container runs as {{ matrix_user_uid }}:{{ matrix_user_gid }} with
  200. TZ={{ matrix_bot_matrix_reminder_bot_reminders_timezone }}
  201. - name: Assert the running container is the version defaults/main.yml pins
  202. ansible.builtin.assert:
  203. that:
  204. - matrix_bot_matrix_reminder_bot_role_defaults.matrix_bot_matrix_reminder_bot_version | string in matrix_bot_matrix_reminder_bot_container.stdout
  205. fail_msg: >-
  206. The running container is {{ matrix_bot_matrix_reminder_bot_container.stdout }}, which
  207. does not carry the pinned version
  208. {{ matrix_bot_matrix_reminder_bot_role_defaults.matrix_bot_matrix_reminder_bot_version }}
  209. success_msg: "The running container is the version defaults/main.yml pins"