diff --git a/roles/web-app-weblate/meta/volumes.yml b/roles/web-app-weblate/meta/volumes.yml index 575d33ffd8..783d3823dd 100644 --- a/roles/web-app-weblate/meta/volumes.yml +++ b/roles/web-app-weblate/meta/volumes.yml @@ -24,7 +24,10 @@ static: mounts: - service: weblate target: &app_static_target /app/cache/static + mode: &app_static_mode "1777" - service: beat target: *app_static_target + mode: *app_static_mode - service: worker target: *app_static_target + mode: *app_static_mode diff --git a/tests/unit/python/roles/web-app-weblate/test_static_root_per_container.py b/tests/unit/python/roles/web-app-weblate/test_static_root_per_container.py index 7b5eb38a3e..0af4ef26fe 100644 --- a/tests/unit/python/roles/web-app-weblate/test_static_root_per_container.py +++ b/tests/unit/python/roles/web-app-weblate/test_static_root_per_container.py @@ -11,6 +11,7 @@ APPLICATION_ID = "web-app-weblate" VOLUMES = PROJECT_ROOT / "roles" / APPLICATION_ID / ROLE_FILE_META_VOLUMES CACHE_DIR = "/app/cache" STATIC_ROOT = f"{CACHE_DIR}/static" +STATIC_MODE = 0o1777 def cache_sharers() -> list[str]: @@ -50,15 +51,30 @@ class TestStaticRootPerContainer(unittest.TestCase): self.assertEqual(sharers, ["beat", "weblate", "worker"]) for service in sharers: with self.subTest(service=service): - self.assertIn( - {"type": "tmpfs", "target": STATIC_ROOT}, - rendered_mounts(service), + static = [ + mount + for mount in rendered_mounts(service) + if isinstance(mount, dict) and mount.get("target") == STATIC_ROOT + ] + self.assertEqual( + [mount.get("type") for mount in static], + ["tmpfs"], f"every Weblate task runs collectstatic into {STATIC_ROOT} on " f"start; on the {CACHE_DIR} volume the tasks share, concurrent " f"runs delete each other's hashed files and the swarm tasks " f"crash-loop with FileNotFoundError, so '{service}' needs a " f"tmpfs of its own there", ) + self.assertEqual( + (static[0].get("tmpfs") or {}).get("mode"), + STATIC_MODE, + f"runc mounts a tmpfs with the mode of the directory beneath " + f"it once that exists, and on the {CACHE_DIR} volume that is " + f"the root-owned 0755 mountpoint the first task left behind; " + f"Weblate runs as uid 1000, so collectstatic dies with " + f"'PermissionError: ... {STATIC_ROOT}/weblate_fonts' unless " + f"the tmpfs of '{service}' declares mode {STATIC_MODE:o}", + ) if __name__ == "__main__":