Matrix Docker Ansible eploy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

187 lines
8.5 KiB

  1. # SPDX-FileCopyrightText: 2026 Slavi Pantaleev
  2. #
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. ---
  5. # Proves the bridge starts, reads the configuration and registration the role rendered, opens
  6. # its appservice port, and is the version the role pins.
  7. #
  8. # It does NOT bridge anything: there is no WhatsApp on the other side, and deliberately never
  9. # will be. See docs/molecule-testing.md.
  10. - name: Verify mautrix-whatsapp
  11. hosts: all
  12. become: true
  13. vars_files:
  14. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
  15. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
  16. gather_facts: false
  17. tasks:
  18. # From the role's own defaults rather than pinned in molecule.yml, so the version
  19. # assertion compares the running image against what the role ships, not the scenario.
  20. - name: Load the role's defaults under a separate name
  21. ansible.builtin.include_vars:
  22. file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
  23. name: mautrix_whatsapp_role_defaults
  24. - name: Wait for the mautrix-whatsapp service to become active
  25. ansible.builtin.systemd_service:
  26. name: matrix-mautrix-whatsapp.service
  27. register: mautrix_whatsapp_service
  28. until: mautrix_whatsapp_service.status.ActiveState == 'active'
  29. retries: 30
  30. delay: 5
  31. failed_when: false
  32. # `Restart=always` means a bridge crash-looping on unreadable config still reports
  33. # `active`, so the restart counter is checked too. Asserted `is defined` because
  34. # `| int` turns a missing property into 0 and would pass vacuously.
  35. - name: Assert the service is active and has not been restarting
  36. ansible.builtin.assert:
  37. that:
  38. - mautrix_whatsapp_service.status.ActiveState == 'active'
  39. - mautrix_whatsapp_service.status.NRestarts is defined
  40. - mautrix_whatsapp_service.status.NRestarts | int == 0
  41. fail_msg: >-
  42. matrix-mautrix-whatsapp.service is
  43. {{ mautrix_whatsapp_service.status.ActiveState | default('unknown') }}
  44. after {{ mautrix_whatsapp_service.status.NRestarts | default('?') }}
  45. automatic restart(s)
  46. success_msg: "matrix-mautrix-whatsapp.service is active and has not restarted"
  47. # The appservice listener is where a homeserver would push transactions. It opening at all
  48. # means the bridge got through reading its configuration and setting itself up.
  49. - name: Wait for the bridge to open its appservice port
  50. ansible.builtin.command:
  51. argv:
  52. - docker
  53. - run
  54. - --rm
  55. - --network={{ matrix_bridge_mautrix_whatsapp_container_network }}
  56. - "{{ molecule_shared_image_curl }}"
  57. - --silent
  58. - --output
  59. - /dev/null
  60. - --write-out
  61. - "HTTP_STATUS=%{http_code}"
  62. - "http://matrix-mautrix-whatsapp:8080/_matrix/mau/live"
  63. register: mautrix_whatsapp_live
  64. changed_when: false
  65. until: "'HTTP_STATUS=000' not in mautrix_whatsapp_live.stdout"
  66. retries: 24
  67. delay: 5
  68. failed_when: false
  69. - name: Assert the bridge answers on its appservice port
  70. ansible.builtin.assert:
  71. that:
  72. - "'HTTP_STATUS=000' not in mautrix_whatsapp_live.stdout"
  73. fail_msg: >-
  74. The bridge did not answer on its appservice port
  75. ({{ mautrix_whatsapp_live.stdout | default('no output') }})
  76. success_msg: "The bridge answers on its appservice port"
  77. - name: Read the configuration the role rendered
  78. ansible.builtin.slurp:
  79. src: "{{ matrix_bridge_mautrix_whatsapp_config_path }}/config.yaml"
  80. register: mautrix_whatsapp_config_file
  81. # Each differs from what the bridge would use on its own, so their presence rules out
  82. # a coincidence.
  83. - name: Assert the rendered configuration carries this scenario's values
  84. ansible.builtin.assert:
  85. that:
  86. - matrix_bridge_mautrix_whatsapp_homeserver_address in mautrix_whatsapp_config_rendered
  87. - matrix_bridge_mautrix_whatsapp_appservice_bot_username in mautrix_whatsapp_config_rendered
  88. - matrix_bridge_mautrix_whatsapp_appservice_token in mautrix_whatsapp_config_rendered
  89. fail_msg: "The rendered configuration does not carry the scenario's values"
  90. success_msg: "The rendered configuration carries the scenario's values"
  91. vars:
  92. mautrix_whatsapp_config_rendered: "{{ mautrix_whatsapp_config_file.content | b64decode }}"
  93. # The role generates the registration; the bridge only consumes it. Worth checking on its
  94. # own, as it is the half of the handshake the homeserver reads.
  95. - name: Read the appservice registration the role rendered
  96. ansible.builtin.slurp:
  97. src: "{{ matrix_bridge_mautrix_whatsapp_config_path }}/registration.yaml"
  98. register: mautrix_whatsapp_registration_file
  99. - name: Assert the registration carries the scenario's tokens and bot user
  100. ansible.builtin.assert:
  101. that:
  102. - matrix_bridge_mautrix_whatsapp_appservice_token in mautrix_whatsapp_registration_rendered
  103. - matrix_bridge_mautrix_whatsapp_homeserver_token in mautrix_whatsapp_registration_rendered
  104. - matrix_bridge_mautrix_whatsapp_appservice_bot_username in mautrix_whatsapp_registration_rendered
  105. fail_msg: "The appservice registration does not carry the scenario's tokens and bot user"
  106. success_msg: "The appservice registration carries the scenario's tokens and bot user"
  107. vars:
  108. mautrix_whatsapp_registration_rendered: "{{ mautrix_whatsapp_registration_file.content | b64decode }}"
  109. # Stronger than the file-on-disk check sqlite allowed: the bridge can only have created
  110. # tables here by resolving the hostname, authenticating with the credentials the role
  111. # rendered, and running its migrations to completion.
  112. - name: List the tables the bridge created in Postgres
  113. ansible.builtin.command:
  114. argv:
  115. - docker
  116. - exec
  117. - matrix-postgres-molecule
  118. - psql
  119. - --username={{ matrix_bridge_mautrix_whatsapp_database_username }}
  120. - --dbname={{ matrix_bridge_mautrix_whatsapp_database_name }}
  121. - --tuples-only
  122. - --no-align
  123. - --command=SELECT tablename FROM pg_tables WHERE schemaname = 'public'
  124. register: mautrix_whatsapp_tables
  125. changed_when: false
  126. - name: Assert the bridge migrated its schema into the database the role pointed it at
  127. ansible.builtin.assert:
  128. that:
  129. - mautrix_whatsapp_tables.rc == 0
  130. - "'version' in mautrix_whatsapp_table_names"
  131. - mautrix_whatsapp_table_names | length > 5
  132. fail_msg: >-
  133. The bridge did not create its schema in
  134. {{ matrix_bridge_mautrix_whatsapp_database_name }}
  135. (found {{ mautrix_whatsapp_table_names | length }} table(s))
  136. success_msg: "The bridge migrated its schema into the database the role pointed it at"
  137. vars:
  138. mautrix_whatsapp_table_names: "{{ mautrix_whatsapp_tables.stdout_lines | select | list }}"
  139. - name: Read the image of the running container
  140. ansible.builtin.command:
  141. argv:
  142. - docker
  143. - container
  144. - inspect
  145. - matrix-mautrix-whatsapp
  146. - --format
  147. - "{{ '{{' }} .Config.Image {{ '}}' }}"
  148. register: mautrix_whatsapp_image
  149. changed_when: false
  150. - name: Assert the running container is the version defaults/main.yml pins
  151. ansible.builtin.assert:
  152. that:
  153. - mautrix_whatsapp_role_defaults.matrix_bridge_mautrix_whatsapp_version in mautrix_whatsapp_image.stdout
  154. fail_msg: >-
  155. The running container is {{ mautrix_whatsapp_image.stdout }}, which does
  156. not carry the pinned version
  157. {{ mautrix_whatsapp_role_defaults.matrix_bridge_mautrix_whatsapp_version }}
  158. success_msg: "The running container is the version defaults/main.yml pins"
  159. - name: Read the labels the role rendered
  160. ansible.builtin.slurp:
  161. src: "{{ matrix_bridge_mautrix_whatsapp_base_path }}/labels"
  162. register: mautrix_whatsapp_labels
  163. - name: Assert no Traefik labels are emitted while Traefik support is disabled
  164. ansible.builtin.assert:
  165. that:
  166. - "'traefik.' not in (mautrix_whatsapp_labels.content | b64decode)"
  167. fail_msg: >-
  168. Traefik labels were emitted even though
  169. matrix_bridge_mautrix_whatsapp_container_labels_traefik_enabled is false
  170. success_msg: "No Traefik labels are emitted while Traefik support is disabled"