Matrix Docker Ansible eploy
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

383 行
21 KiB

  1. # SPDX-FileCopyrightText: 2026 Slavi Pantaleev
  2. #
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. ---
  5. # FluffyChat Web is a static browser client. The live JSON configuration and a real
  6. # application asset are the weight-bearing probes; no homeserver account is needed.
  7. - name: Verify matrix-client-fluffychat
  8. hosts: all
  9. become: true
  10. vars_files:
  11. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
  12. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
  13. vars:
  14. matrix_client_fluffychat_root_body: "{{ matrix_client_fluffychat_root_http.stdout_lines[:-1] | join('\n') }}"
  15. matrix_client_fluffychat_runtime: "{{ (matrix_client_fluffychat_container_inspect.stdout | from_json) | first }}"
  16. gather_facts: false
  17. tasks:
  18. # Load the pin independently of the scenario so a role version bump advances
  19. # the expected image instead of comparing two duplicated test values.
  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: matrix_client_fluffychat_role_defaults
  24. - name: Wait for the matrix-client-fluffychat service to become active
  25. ansible.builtin.systemd_service:
  26. name: matrix-client-fluffychat.service
  27. register: matrix_client_fluffychat_service
  28. until: matrix_client_fluffychat_service.status.ActiveState == 'active'
  29. retries: 30
  30. delay: 5
  31. failed_when: false
  32. # Restart=always makes ActiveState alone green even during a crash loop.
  33. - name: Assert the service is active and has not restarted
  34. ansible.builtin.assert:
  35. that:
  36. - matrix_client_fluffychat_service.status.ActiveState == 'active'
  37. - matrix_client_fluffychat_service.status.NRestarts is defined
  38. - matrix_client_fluffychat_service.status.NRestarts | int == 0
  39. fail_msg: >-
  40. matrix-client-fluffychat.service is
  41. {{ matrix_client_fluffychat_service.status.ActiveState | default('unknown') }} after
  42. {{ matrix_client_fluffychat_service.status.NRestarts | default('?') }} restart(s)
  43. success_msg: "matrix-client-fluffychat.service is active and has not restarted"
  44. # Probe through the private container network, matching how Traefik reaches the
  45. # service in a deployment without publishing an artificial host port.
  46. - name: Wait for FluffyChat to serve its application root
  47. ansible.builtin.command:
  48. argv:
  49. - docker
  50. - run
  51. - --rm
  52. - --network={{ matrix_client_fluffychat_container_network }}
  53. - "{{ molecule_shared_image_curl }}"
  54. - --silent
  55. - --show-error
  56. - --write-out
  57. - "\nHTTP_STATUS=%{http_code}"
  58. - "http://matrix-client-fluffychat:{{ matrix_client_fluffychat_container_http_port }}/"
  59. register: matrix_client_fluffychat_root_http
  60. changed_when: false
  61. until: "'HTTP_STATUS=200' in matrix_client_fluffychat_root_http.stdout"
  62. retries: 24
  63. delay: 5
  64. failed_when: false
  65. - name: Assert the application root is real FluffyChat HTML
  66. ansible.builtin.assert:
  67. that:
  68. - matrix_client_fluffychat_root_http.rc == 0
  69. - matrix_client_fluffychat_root_http.stdout_lines[-1] == 'HTTP_STATUS=200'
  70. - matrix_client_fluffychat_root_body | trim | length > 500
  71. - "'<!doctype html' in matrix_client_fluffychat_root_body | lower"
  72. - "'fluffychat' in matrix_client_fluffychat_root_body | lower"
  73. fail_msg: "FluffyChat did not return its real application HTML over the container network"
  74. success_msg: "FluffyChat serves its real application root over HTTP"
  75. - name: Fetch FluffyChat's live browser configuration
  76. ansible.builtin.command:
  77. argv:
  78. - docker
  79. - run
  80. - --rm
  81. - --network={{ matrix_client_fluffychat_container_network }}
  82. - "{{ molecule_shared_image_curl }}"
  83. - --silent
  84. - --show-error
  85. - --write-out
  86. - "\nHTTP_STATUS=%{http_code}\nCONTENT_TYPE=%{content_type}"
  87. - "http://matrix-client-fluffychat:{{ matrix_client_fluffychat_container_http_port }}/config.json"
  88. register: matrix_client_fluffychat_config_http
  89. changed_when: false
  90. failed_when: false
  91. - name: Assert the live browser configuration is served as JSON
  92. ansible.builtin.assert:
  93. that:
  94. - matrix_client_fluffychat_config_http.rc == 0
  95. - "'HTTP_STATUS=200' in matrix_client_fluffychat_config_http.stdout_lines"
  96. - matrix_client_fluffychat_config_http.stdout | regex_search('(?m)^CONTENT_TYPE=application/json(?:;|$)') is not none
  97. fail_msg: "FluffyChat did not serve its live browser configuration as JSON"
  98. success_msg: "FluffyChat serves its live browser configuration as JSON"
  99. - name: Parse the live browser configuration
  100. ansible.builtin.set_fact:
  101. matrix_client_fluffychat_live_config: >-
  102. {{ matrix_client_fluffychat_config_http.stdout | regex_replace('\nHTTP_STATUS=[0-9]+\nCONTENT_TYPE=.*$', '') | from_json }}
  103. - name: Assert the live configuration carries the configured homeserver
  104. ansible.builtin.assert:
  105. that:
  106. - matrix_client_fluffychat_live_config is mapping
  107. - matrix_client_fluffychat_live_config.keys() | list == ['defaultHomeserver']
  108. - matrix_client_fluffychat_live_config.defaultHomeserver == matrix_client_fluffychat_config_defaultHomeserver
  109. fail_msg: "The live config.json does not carry the non-default homeserver rendered by the role"
  110. success_msg: "The live config.json carries the configured homeserver"
  111. # Fetch a browser bundle referenced by the live HTML rather than assuming the
  112. # container is useful just because nginx answered at its root.
  113. - name: Extract a live FluffyChat application asset path
  114. ansible.builtin.set_fact:
  115. matrix_client_fluffychat_application_asset_path: >-
  116. {{ matrix_client_fluffychat_root_body | regex_search('(?<=src=")[^"]+[.]js(?=")') }}
  117. - name: Fetch the live FluffyChat application asset
  118. ansible.builtin.command:
  119. argv:
  120. - docker
  121. - run
  122. - --rm
  123. - --network={{ matrix_client_fluffychat_container_network }}
  124. - "{{ molecule_shared_image_curl }}"
  125. - --silent
  126. - --show-error
  127. - --output
  128. - /dev/null
  129. - --write-out
  130. - "HTTP_STATUS=%{http_code}\nCONTENT_TYPE=%{content_type}\nSIZE=%{size_download}"
  131. - "http://matrix-client-fluffychat:{{ matrix_client_fluffychat_container_http_port }}/{{ matrix_client_fluffychat_application_asset_path }}"
  132. register: matrix_client_fluffychat_asset_http
  133. changed_when: false
  134. failed_when: false
  135. - name: Assert a real FluffyChat application asset is served
  136. ansible.builtin.assert:
  137. that:
  138. - matrix_client_fluffychat_asset_http.rc == 0
  139. - "'HTTP_STATUS=200' in matrix_client_fluffychat_asset_http.stdout_lines"
  140. - matrix_client_fluffychat_asset_http.stdout | regex_search('(?m)^CONTENT_TYPE=(?:application|text)/javascript(?:;|$)') is not none
  141. - matrix_client_fluffychat_asset_http.stdout | regex_search('(?m)^SIZE=([1-9][0-9]{3,})$') is not none
  142. fail_msg: "FluffyChat did not serve the JavaScript asset referenced by its live HTML"
  143. success_msg: "FluffyChat serves a substantial live application asset"
  144. - name: Read the configuration file the role rendered
  145. ansible.builtin.slurp:
  146. src: "{{ matrix_client_fluffychat_config_path }}/config.json"
  147. register: matrix_client_fluffychat_rendered_config_file
  148. - name: Assert the served and rendered structured configurations are identical
  149. ansible.builtin.assert:
  150. that:
  151. - matrix_client_fluffychat_rendered_config == matrix_client_fluffychat_live_config
  152. fail_msg: "The live configuration differs from the parsed file the role rendered"
  153. success_msg: "The running service exposes the exact structured configuration the role rendered"
  154. vars:
  155. matrix_client_fluffychat_rendered_config: >-
  156. {{ matrix_client_fluffychat_rendered_config_file.content | b64decode | from_json }}
  157. - name: Read the labels file the role rendered
  158. ansible.builtin.slurp:
  159. src: "{{ matrix_client_fluffychat_base_path }}/labels"
  160. register: matrix_client_fluffychat_labels_file
  161. - name: Initialize the parsed labels
  162. ansible.builtin.set_fact:
  163. matrix_client_fluffychat_labels_parsed: {}
  164. - name: Parse the rendered labels
  165. ansible.builtin.set_fact:
  166. matrix_client_fluffychat_labels_parsed: >-
  167. {{ matrix_client_fluffychat_labels_parsed | combine({item.split('=', 1)[0]: item.split('=', 1)[1]}) }}
  168. loop: "{{ (matrix_client_fluffychat_labels_file.content | b64decode).splitlines() | reject('equalto', '') }}"
  169. when: "'=' in item"
  170. no_log: true
  171. - name: Assert the rendered labels carry the public routing contract
  172. ansible.builtin.assert:
  173. that:
  174. - matrix_client_fluffychat_labels_parsed['traefik.enable'] == 'true'
  175. - matrix_client_fluffychat_labels_parsed['traefik.docker.network'] == matrix_client_fluffychat_container_network
  176. - matrix_client_fluffychat_labels_parsed['traefik.http.services.matrix-client-fluffychat.loadbalancer.server.port'] == (matrix_client_fluffychat_container_http_port | string)
  177. - matrix_client_fluffychat_labels_parsed['traefik.http.routers.matrix-client-fluffychat.rule'] == 'Host(`chat.fluffy.molecule.local`) && PathPrefix(`/fluffy`)'
  178. - matrix_client_fluffychat_labels_parsed['traefik.http.routers.matrix-client-fluffychat.priority'] == '731'
  179. - matrix_client_fluffychat_labels_parsed['traefik.http.routers.matrix-client-fluffychat.entrypoints'] == 'web'
  180. - matrix_client_fluffychat_labels_parsed['traefik.http.routers.matrix-client-fluffychat.tls'] == 'false'
  181. - matrix_client_fluffychat_labels_parsed['traefik.http.routers.matrix-client-fluffychat.middlewares'] == 'matrix-client-fluffychat-slashless-redirect,matrix-client-fluffychat-strip-prefix,matrix-client-fluffychat-add-headers'
  182. - matrix_client_fluffychat_labels_parsed['molecule.fluffychat.coverage'] == 'enabled'
  183. fail_msg: "The parsed labels do not carry the scenario's routing contract"
  184. success_msg: "The parsed labels carry the scenario's routing contract"
  185. - name: Assert the rendered labels carry the configured response headers
  186. ansible.builtin.assert:
  187. that:
  188. - matrix_client_fluffychat_labels_parsed['traefik.http.middlewares.matrix-client-fluffychat-add-headers.headers.customresponseheaders.X-XSS-Protection'] == '0'
  189. - matrix_client_fluffychat_labels_parsed['traefik.http.middlewares.matrix-client-fluffychat-add-headers.headers.customresponseheaders.X-Content-Type-Options'] == 'molecule-nosniff'
  190. - matrix_client_fluffychat_labels_parsed['traefik.http.middlewares.matrix-client-fluffychat-add-headers.headers.customresponseheaders.Content-Security-Policy'] == 'frame-ancestors https://embed.molecule.local'
  191. - matrix_client_fluffychat_labels_parsed['traefik.http.middlewares.matrix-client-fluffychat-add-headers.headers.customresponseheaders.Permission-Policy'] == 'camera=(self)'
  192. - "'traefik.http.middlewares.matrix-client-fluffychat-add-headers.headers.customresponseheaders.Strict-Transport-Security' not in matrix_client_fluffychat_labels_parsed"
  193. fail_msg: "The parsed labels lost response headers or incorrectly enable HSTS without TLS"
  194. success_msg: "The parsed labels carry the configured response-header contract"
  195. - name: Inspect the running FluffyChat container
  196. ansible.builtin.command:
  197. argv:
  198. - docker
  199. - container
  200. - inspect
  201. - matrix-client-fluffychat
  202. register: matrix_client_fluffychat_container_inspect
  203. changed_when: false
  204. - name: Assert the rendered labels reached the running container
  205. ansible.builtin.assert:
  206. that:
  207. - matrix_client_fluffychat_runtime.Config.Labels['traefik.enable'] == 'true'
  208. - matrix_client_fluffychat_runtime.Config.Labels['traefik.docker.network'] == matrix_client_fluffychat_container_network
  209. - matrix_client_fluffychat_runtime.Config.Labels['traefik.http.services.matrix-client-fluffychat.loadbalancer.server.port'] == (matrix_client_fluffychat_container_http_port | string)
  210. - matrix_client_fluffychat_runtime.Config.Labels['traefik.http.routers.matrix-client-fluffychat.priority'] == '731'
  211. - matrix_client_fluffychat_runtime.Config.Labels['molecule.fluffychat.coverage'] == 'enabled'
  212. - matrix_client_fluffychat_runtime.Config.Labels['molecule.fluffychat.extra-argument'] == 'reached'
  213. fail_msg: "The running container does not carry the labels the role rendered and passed"
  214. success_msg: "The configured labels reached the running container"
  215. - name: Assert the configured internal port reached the running container
  216. ansible.builtin.assert:
  217. that:
  218. - "('SERVER_PORT=' ~ (matrix_client_fluffychat_container_http_port | string)) in matrix_client_fluffychat_runtime.Config.Env"
  219. fail_msg: "The running container did not receive its configured internal HTTP port"
  220. success_msg: "The configured internal HTTP port reached the running container"
  221. - name: Assert the running container uses the exact image and version the role pins
  222. ansible.builtin.assert:
  223. that:
  224. - matrix_client_fluffychat_runtime.Config.Image == matrix_client_fluffychat_expected_image
  225. fail_msg: >-
  226. The running container uses {{ matrix_client_fluffychat_runtime.Config.Image }},
  227. expected {{ matrix_client_fluffychat_expected_image }}
  228. success_msg: "The running container uses the exact image and version defaults/main.yml pins"
  229. vars:
  230. matrix_client_fluffychat_expected_image: >-
  231. {{ matrix_client_fluffychat_role_defaults.matrix_client_fluffychat_container_image_registry_prefix_upstream_default }}etkecc/fluffychat-web:{{ matrix_client_fluffychat_role_defaults.matrix_client_fluffychat_version }}
  232. - name: Assert the running container uses the configured UID and GID
  233. ansible.builtin.assert:
  234. that:
  235. - matrix_client_fluffychat_runtime.Config.User == (matrix_user_uid | string) ~ ':' ~ (matrix_user_gid | string)
  236. fail_msg: >-
  237. The running container uses {{ matrix_client_fluffychat_runtime.Config.User }},
  238. expected {{ matrix_user_uid }}:{{ matrix_user_gid }}
  239. success_msg: "The running container uses the configured UID and GID"
  240. - name: Assert the running container keeps the image command contract
  241. ansible.builtin.assert:
  242. that:
  243. - matrix_client_fluffychat_runtime.Config.Entrypoint == ['/usr/local/bin/entrypoint.sh']
  244. - matrix_client_fluffychat_runtime.Config.Cmd == ['static-web-server']
  245. - matrix_client_fluffychat_runtime.HostConfig.RestartPolicy.Name == 'no'
  246. - matrix_client_fluffychat_runtime.HostConfig.AutoRemove is sameas true
  247. - matrix_client_fluffychat_runtime.HostConfig.LogConfig.Type == 'none'
  248. fail_msg: >-
  249. FluffyChat has unexpected command or Docker lifecycle values:
  250. entrypoint={{ matrix_client_fluffychat_runtime.Config.Entrypoint }},
  251. command={{ matrix_client_fluffychat_runtime.Config.Cmd }},
  252. restart={{ matrix_client_fluffychat_runtime.HostConfig.RestartPolicy.Name }},
  253. auto-remove={{ matrix_client_fluffychat_runtime.HostConfig.AutoRemove }},
  254. log-driver={{ matrix_client_fluffychat_runtime.HostConfig.LogConfig.Type }}
  255. success_msg: "The role preserves the image command and exact Docker lifecycle contract"
  256. - name: Assert the running container security contract
  257. ansible.builtin.assert:
  258. that:
  259. - matrix_client_fluffychat_runtime.HostConfig.ReadonlyRootfs is sameas true
  260. - matrix_client_fluffychat_runtime.HostConfig.Privileged is sameas false
  261. - matrix_client_fluffychat_runtime.HostConfig.CapAdd | default([], true) | length == 0
  262. - matrix_client_fluffychat_runtime.HostConfig.CapDrop == ['ALL']
  263. fail_msg: "The FluffyChat container is missing its read-only root or dropped capabilities"
  264. success_msg: "The FluffyChat container has a read-only root and all capabilities dropped"
  265. - name: Assert the intended writable path is a constrained tmpfs mount
  266. ansible.builtin.assert:
  267. that:
  268. - matrix_client_fluffychat_runtime.HostConfig.Tmpfs.keys() | list == ['/tmp']
  269. - matrix_client_fluffychat_runtime.HostConfig.Tmpfs['/tmp'] == 'rw,noexec,nosuid,size=10m'
  270. fail_msg: "The running container does not have the role's exact /tmp tmpfs contract"
  271. success_msg: "The running container has the exact constrained /tmp tmpfs mount"
  272. - name: Assert the role's configuration is mounted read-only at the image path
  273. ansible.builtin.assert:
  274. that:
  275. - matrix_client_fluffychat_runtime.Mounts | length == 1
  276. - matrix_client_fluffychat_runtime.Mounts[0].Type == 'bind'
  277. - matrix_client_fluffychat_runtime.Mounts[0].Source == matrix_client_fluffychat_config_path ~ '/config.json'
  278. - matrix_client_fluffychat_runtime.Mounts[0].Destination == '/var/public/config.json'
  279. - matrix_client_fluffychat_runtime.Mounts[0].RW is sameas false
  280. fail_msg: "The role-rendered config.json is not the container's sole read-only bind mount"
  281. success_msg: "The role-rendered config.json is mounted read-only at the image's live path"
  282. - name: Assert the running container is attached only to its dedicated network
  283. ansible.builtin.assert:
  284. that:
  285. - matrix_client_fluffychat_runtime.HostConfig.NetworkMode == matrix_client_fluffychat_container_network
  286. - matrix_client_fluffychat_runtime.NetworkSettings.Networks is mapping
  287. - matrix_client_fluffychat_runtime.NetworkSettings.Networks | length == 1
  288. - matrix_client_fluffychat_container_network in matrix_client_fluffychat_runtime.NetworkSettings.Networks
  289. fail_msg: >-
  290. FluffyChat has unexpected network attachments:
  291. {{ matrix_client_fluffychat_runtime.NetworkSettings.Networks.keys() | list }}
  292. success_msg: "The running container is attached only to its dedicated network"
  293. - name: Assert the observable extra runtime argument reached Docker
  294. ansible.builtin.assert:
  295. that:
  296. - matrix_client_fluffychat_runtime.Config.Hostname == 'fluffychat-molecule-runtime'
  297. fail_msg: "The configured container hostname extra argument did not reach Docker"
  298. success_msg: "The configured container extra argument reached Docker"
  299. - name: Probe the image's default HTTP port
  300. ansible.builtin.command:
  301. argv:
  302. - docker
  303. - run
  304. - --rm
  305. - --network={{ matrix_client_fluffychat_container_network }}
  306. - "{{ molecule_shared_image_curl }}"
  307. - --silent
  308. - --show-error
  309. - --connect-timeout
  310. - "2"
  311. - --max-time
  312. - "3"
  313. - --output
  314. - /dev/null
  315. - --write-out
  316. - "HTTP_STATUS=%{http_code}"
  317. - http://matrix-client-fluffychat:8080/
  318. register: matrix_client_fluffychat_default_port_http
  319. changed_when: false
  320. failed_when: false
  321. - name: Assert the image's default HTTP port is closed
  322. ansible.builtin.assert:
  323. that:
  324. - matrix_client_fluffychat_default_port_http.rc != 0
  325. - matrix_client_fluffychat_default_port_http.stdout != 'HTTP_STATUS=200'
  326. fail_msg: "FluffyChat still listens on the image's default port instead of only the configured port"
  327. success_msg: "FluffyChat no longer listens on the image's default HTTP port"
  328. - name: Ask Docker for FluffyChat's published ports
  329. ansible.builtin.command:
  330. argv:
  331. - docker
  332. - container
  333. - port
  334. - matrix-client-fluffychat
  335. register: matrix_client_fluffychat_published_ports
  336. changed_when: false
  337. failed_when: false
  338. - name: Assert the role publishes no host ports
  339. ansible.builtin.assert:
  340. that:
  341. - matrix_client_fluffychat_runtime.HostConfig.PortBindings | default({}, true) | length == 0
  342. - matrix_client_fluffychat_published_ports.rc == 0
  343. - matrix_client_fluffychat_published_ports.stdout | trim | length == 0
  344. fail_msg: >-
  345. FluffyChat unexpectedly publishes a host port:
  346. {{ matrix_client_fluffychat_published_ports.stdout | default('unknown') }}
  347. success_msg: "The role leaves FluffyChat's HTTP port unpublished"