Matrix Docker Ansible eploy
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

361 righe
16 KiB

  1. # SPDX-FileCopyrightText: 2026 Slavi Pantaleev
  2. #
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. ---
  5. # Proves a single main Synapse process starts on the non-default listener the role
  6. # rendered, serves real Matrix APIs, migrates the configured Postgres database, and
  7. # carries no worker, Redis, S3, MAS, email, or other optional-integration topology.
  8. - name: Verify Synapse
  9. hosts: all
  10. become: true
  11. vars_files:
  12. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
  13. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
  14. gather_facts: false
  15. vars:
  16. matrix_synapse_config: "{{ matrix_synapse_config_file.content | b64decode | from_yaml }}"
  17. matrix_synapse_http_listeners: >-
  18. {{ matrix_synapse_config.listeners | selectattr('type', 'equalto', 'http') | list }}
  19. matrix_synapse_http_listener: "{{ matrix_synapse_http_listeners | first | default({}) }}"
  20. tasks:
  21. # The image version comes from the role, not from the scenario, so an assertion cannot
  22. # remain green merely because both the scenario and runtime are pinned to the same typo.
  23. - name: Load the role's defaults under a separate name
  24. ansible.builtin.include_vars:
  25. file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
  26. name: matrix_synapse_role_defaults
  27. - name: Wait for the Synapse service to become active
  28. ansible.builtin.systemd_service:
  29. name: matrix-synapse.service
  30. register: matrix_synapse_service
  31. until: matrix_synapse_service.status.ActiveState == 'active'
  32. retries: 30
  33. delay: 5
  34. failed_when: false
  35. - name: Assert the Synapse service is stable
  36. ansible.builtin.assert:
  37. that:
  38. - matrix_synapse_service.status.ActiveState == 'active'
  39. - matrix_synapse_service.status.NRestarts is defined
  40. - matrix_synapse_service.status.NRestarts | int == 0
  41. fail_msg: >-
  42. matrix-synapse.service is
  43. {{ matrix_synapse_service.status.ActiveState | default('unknown') }} after
  44. {{ matrix_synapse_service.status.NRestarts | default('?') }} restart(s)
  45. success_msg: "matrix-synapse.service is active and has not restarted"
  46. # Probe over the role's own network. No host port is published, matching deployment.
  47. - name: Request the Synapse health endpoint
  48. ansible.builtin.command:
  49. argv:
  50. - docker
  51. - run
  52. - --rm
  53. - --network={{ matrix_synapse_container_network }}
  54. - "{{ molecule_shared_image_curl }}"
  55. - --silent
  56. - --show-error
  57. - --write-out
  58. - "\nHTTP_STATUS=%{http_code}"
  59. - http://matrix-synapse:{{ matrix_synapse_container_client_api_port }}/health
  60. register: matrix_synapse_health
  61. changed_when: false
  62. until: "'HTTP_STATUS=200' in matrix_synapse_health.stdout"
  63. retries: 24
  64. delay: 5
  65. failed_when: false
  66. - name: Request the Synapse Matrix versions endpoint
  67. ansible.builtin.command:
  68. argv:
  69. - docker
  70. - run
  71. - --rm
  72. - --network={{ matrix_synapse_container_network }}
  73. - "{{ molecule_shared_image_curl }}"
  74. - --silent
  75. - --show-error
  76. - --write-out
  77. - "\nHTTP_STATUS=%{http_code}"
  78. - http://matrix-synapse:{{ matrix_synapse_container_client_api_port }}/_matrix/client/versions
  79. register: matrix_synapse_versions_response
  80. changed_when: false
  81. until: "'HTTP_STATUS=200' in matrix_synapse_versions_response.stdout"
  82. retries: 24
  83. delay: 5
  84. failed_when: false
  85. - name: Assert Synapse serves its health endpoint
  86. ansible.builtin.assert:
  87. that:
  88. - matrix_synapse_health.rc == 0
  89. - "'HTTP_STATUS=200' in matrix_synapse_health.stdout"
  90. - matrix_synapse_health.stdout.startswith('OK')
  91. fail_msg: >-
  92. Synapse did not serve /health on port
  93. {{ matrix_synapse_container_client_api_port }}
  94. success_msg: "Synapse serves /health"
  95. - name: Assert Synapse serves its Matrix versions endpoint
  96. ansible.builtin.assert:
  97. that:
  98. - matrix_synapse_versions_response.rc == 0
  99. - "'HTTP_STATUS=200' in matrix_synapse_versions_response.stdout"
  100. fail_msg: >-
  101. Synapse did not serve /_matrix/client/versions on port
  102. {{ matrix_synapse_container_client_api_port }}
  103. success_msg: "Synapse serves /_matrix/client/versions"
  104. - name: Parse the Matrix versions response
  105. ansible.builtin.set_fact:
  106. matrix_synapse_versions: >-
  107. {{ matrix_synapse_versions_response.stdout
  108. | regex_replace('\nHTTP_STATUS=200$', '')
  109. | from_json }}
  110. - name: Assert the versions response is a real Matrix API document
  111. ansible.builtin.assert:
  112. that:
  113. - matrix_synapse_versions.versions is sequence
  114. - matrix_synapse_versions.versions | length > 0
  115. - "'v1.1' in matrix_synapse_versions.versions"
  116. - matrix_synapse_versions.unstable_features is mapping
  117. fail_msg: "The versions endpoint did not return a Matrix versions document"
  118. success_msg: "The versions endpoint returns a Matrix versions document"
  119. # The role moved the listener away from 8008. Refusing the upstream default port proves
  120. # the successful requests above did not accidentally hit an unchanged default listener.
  121. - name: Request the upstream-default Synapse port
  122. ansible.builtin.command:
  123. argv:
  124. - docker
  125. - run
  126. - --rm
  127. - --network={{ matrix_synapse_container_network }}
  128. - "{{ molecule_shared_image_curl }}"
  129. - --silent
  130. - --output
  131. - /dev/null
  132. - --connect-timeout
  133. - '2'
  134. - --max-time
  135. - '3'
  136. - http://matrix-synapse:8008/health
  137. register: matrix_synapse_default_listener
  138. changed_when: false
  139. failed_when: false
  140. - name: Assert Synapse does not listen on the upstream-default port
  141. ansible.builtin.assert:
  142. that:
  143. - matrix_synapse_default_listener.rc != 0
  144. fail_msg: "Synapse still answers on port 8008, so the non-default listener is not proven"
  145. success_msg: "Only the role-configured client listener answers"
  146. - name: Read the homeserver configuration the role rendered
  147. ansible.builtin.slurp:
  148. src: "{{ matrix_synapse_config_dir_path }}/homeserver.yaml"
  149. register: matrix_synapse_config_file
  150. - name: Read the homeserver configuration identity
  151. ansible.builtin.stat:
  152. path: "{{ matrix_synapse_config_dir_path }}/homeserver.yaml"
  153. register: matrix_synapse_config_stat
  154. - name: Assert the parsed homeserver identity and behavior
  155. ansible.builtin.assert:
  156. that:
  157. - matrix_synapse_config.server_name == matrix_domain
  158. - matrix_synapse_config.public_baseurl == matrix_synapse_public_baseurl
  159. - matrix_synapse_config.max_upload_size == (matrix_synapse_max_upload_size_mb | string) + 'M'
  160. - not matrix_synapse_config.presence.enabled
  161. - not matrix_synapse_config.url_preview_enabled
  162. - not matrix_synapse_config.allow_public_rooms_over_federation
  163. - matrix_synapse_config.user_directory.search_all_users
  164. - matrix_synapse_config.user_directory.prefer_local_users
  165. - matrix_synapse_config.trusted_key_servers == []
  166. fail_msg: "The parsed homeserver configuration does not carry the scenario's values"
  167. success_msg: "The parsed homeserver configuration carries the scenario's values"
  168. - name: Assert the parsed homeserver secrets
  169. ansible.builtin.assert:
  170. that:
  171. - matrix_synapse_config.macaroon_secret_key == matrix_synapse_macaroon_secret_key
  172. - matrix_synapse_config.registration_shared_secret == matrix_synapse_registration_shared_secret
  173. - matrix_synapse_config.form_secret == matrix_synapse_form_secret
  174. - matrix_synapse_config.password_config.pepper == matrix_synapse_password_config_pepper
  175. fail_msg: "The parsed homeserver configuration does not carry the scenario's secrets"
  176. success_msg: "The parsed homeserver configuration carries the scenario's secrets"
  177. no_log: true
  178. - name: Assert the parsed client listener topology
  179. ansible.builtin.assert:
  180. that:
  181. - matrix_synapse_config.listeners | length == 1
  182. - matrix_synapse_http_listeners | length == 1
  183. - matrix_synapse_http_listener.port == matrix_synapse_container_client_api_port
  184. - not matrix_synapse_http_listener.tls
  185. - matrix_synapse_http_listener.bind_addresses == ['::']
  186. - matrix_synapse_http_listener.resources | length == 1
  187. - matrix_synapse_http_listener.resources[0].names == ['client']
  188. fail_msg: "The parsed homeserver configuration has an unexpected listener topology"
  189. success_msg: "The parsed homeserver configuration has one non-default client listener"
  190. - name: Assert the parsed Postgres configuration
  191. ansible.builtin.assert:
  192. that:
  193. - matrix_synapse_config.database.name == 'psycopg2'
  194. - matrix_synapse_config.database.args.host == matrix_synapse_database_host
  195. - matrix_synapse_config.database.args.port == matrix_synapse_database_port
  196. - matrix_synapse_config.database.args.user == matrix_synapse_database_user
  197. - matrix_synapse_config.database.args.password == matrix_synapse_database_password
  198. - matrix_synapse_config.database.args.database == matrix_synapse_database_database
  199. - matrix_synapse_config.database.args.cp_min == matrix_synapse_database_cp_min
  200. - matrix_synapse_config.database.args.cp_max == matrix_synapse_database_cp_max
  201. fail_msg: "The parsed homeserver configuration does not point at scenario Postgres"
  202. success_msg: "The parsed homeserver configuration points at scenario Postgres"
  203. no_log: true
  204. - name: Assert optional Synapse topology is absent
  205. ansible.builtin.assert:
  206. that:
  207. - not matrix_synapse_config.redis.enabled
  208. - matrix_synapse_config.media_storage_providers == []
  209. - matrix_synapse_config.modules == []
  210. - "'matrix_authentication_service' not in matrix_synapse_config"
  211. - "'email' not in matrix_synapse_config"
  212. - "'worker_app' not in matrix_synapse_config"
  213. fail_msg: "An excluded worker, Redis, S3, MAS, email, or module integration is configured"
  214. success_msg: "The configuration contains only the intended main-process topology"
  215. - name: Assert the homeserver configuration file identity
  216. ansible.builtin.assert:
  217. that:
  218. - matrix_synapse_config_stat.stat.uid | int == matrix_user_uid | int
  219. - matrix_synapse_config_stat.stat.gid | int == matrix_user_gid | int
  220. - matrix_synapse_config_stat.stat.mode == '0644'
  221. fail_msg: "homeserver.yaml has the wrong ownership or mode"
  222. success_msg: "homeserver.yaml has the playbook identity and expected mode"
  223. # These tables appear only after hostname resolution, authentication, and genuine Synapse
  224. # migrations against the exact non-default database prepared for the scenario.
  225. - name: List the tables Synapse created in Postgres
  226. ansible.builtin.command:
  227. argv:
  228. - docker
  229. - exec
  230. - matrix-postgres-molecule
  231. - psql
  232. - --username={{ matrix_synapse_database_user }}
  233. - --dbname={{ matrix_synapse_database_database }}
  234. - --tuples-only
  235. - --no-align
  236. - --command=SELECT tablename FROM pg_tables WHERE schemaname = 'public'
  237. register: matrix_synapse_tables
  238. changed_when: false
  239. - name: Assert Synapse migrated its Postgres schema
  240. ansible.builtin.assert:
  241. that:
  242. - matrix_synapse_tables.rc == 0
  243. - "'schema_version' in matrix_synapse_table_names"
  244. - "'users' in matrix_synapse_table_names"
  245. - "'rooms' in matrix_synapse_table_names"
  246. - "'events' in matrix_synapse_table_names"
  247. - "'state_groups' in matrix_synapse_table_names"
  248. - matrix_synapse_table_names | length > 50
  249. fail_msg: >-
  250. Synapse did not migrate its schema in {{ matrix_synapse_database_database }}
  251. (found {{ matrix_synapse_table_names | length }} table(s))
  252. success_msg: "Synapse genuinely migrated the configured Postgres database"
  253. vars:
  254. matrix_synapse_table_names: "{{ matrix_synapse_tables.stdout_lines | select | list }}"
  255. - name: Inspect the running Synapse container
  256. ansible.builtin.command:
  257. argv:
  258. - docker
  259. - container
  260. - inspect
  261. - matrix-synapse
  262. register: matrix_synapse_container_inspect
  263. changed_when: false
  264. - name: Parse the running Synapse container inspection
  265. ansible.builtin.set_fact:
  266. matrix_synapse_container: "{{ (matrix_synapse_container_inspect.stdout | from_json) | first }}"
  267. - name: Assert the running container uses the exact pinned image
  268. ansible.builtin.assert:
  269. that:
  270. - matrix_synapse_container.Config.Image == 'ghcr.io/element-hq/synapse:' + matrix_synapse_role_defaults.matrix_synapse_version
  271. fail_msg: "The running container does not use the exact image defaults/main.yml pins"
  272. success_msg: "The running container uses the exact image defaults/main.yml pins"
  273. - name: Assert the running container uses the scenario identity
  274. ansible.builtin.assert:
  275. that:
  276. - matrix_synapse_container.Config.User == (matrix_user_uid | string) + ':' + (matrix_user_gid | string)
  277. fail_msg: "The running container does not use the playbook-supplied identity"
  278. success_msg: "The running container uses the playbook-supplied identity"
  279. - name: Assert the running container security isolation
  280. ansible.builtin.assert:
  281. that:
  282. - matrix_synapse_container.HostConfig.ReadonlyRootfs
  283. - "'ALL' in matrix_synapse_container.HostConfig.CapDrop"
  284. fail_msg: "The running container does not have read-only, capability-dropped isolation"
  285. success_msg: "The running container has read-only, capability-dropped isolation"
  286. - name: Assert the running container has a read-only config mount
  287. ansible.builtin.assert:
  288. that:
  289. - matrix_synapse_config_mount | length > 0
  290. - not matrix_synapse_config_mount.RW | default(true)
  291. fail_msg: "The running container does not mount its configuration read-only"
  292. success_msg: "The running container mounts its configuration read-only"
  293. vars:
  294. matrix_synapse_config_mount: >-
  295. {{ matrix_synapse_container.Mounts
  296. | selectattr('Destination', 'equalto', '/data')
  297. | first | default({}) }}
  298. - name: Assert the running container has a writable storage mount
  299. ansible.builtin.assert:
  300. that:
  301. - matrix_synapse_storage_mount | length > 0
  302. - matrix_synapse_storage_mount.RW | default(false)
  303. fail_msg: "The running container does not mount its media storage writable"
  304. success_msg: "The running container mounts its media storage writable"
  305. vars:
  306. matrix_synapse_storage_mount: >-
  307. {{ matrix_synapse_container.Mounts
  308. | selectattr('Destination', 'equalto', '/matrix-media-store-parent')
  309. | first | default({}) }}
  310. - name: Assert the running container is healthy
  311. ansible.builtin.assert:
  312. that:
  313. - matrix_synapse_container.State.Health.Status == 'healthy'
  314. fail_msg: "The running container is not healthy"
  315. success_msg: "The running container is healthy"
  316. - name: Assert the running container has only its private network
  317. ansible.builtin.assert:
  318. that:
  319. - matrix_synapse_container_network in matrix_synapse_container.NetworkSettings.Networks
  320. - matrix_synapse_container.NetworkSettings.Networks | length == 1
  321. fail_msg: "The running container has unexpected network attachments"
  322. success_msg: "The running container has only its private network"
  323. - name: Assert the running container publishes no host ports
  324. ansible.builtin.assert:
  325. that:
  326. - matrix_synapse_container.HostConfig.PortBindings | default({}, true) | length == 0
  327. fail_msg: "The running container unexpectedly publishes a host port"
  328. success_msg: "The running container publishes no host ports"