mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-13 06:06:38 +00:00
CI run 40979252521 Weblate#1 (swarm) never converged: every task after
the first died in collectstatic with "PermissionError: [Errno 13]
Permission denied: '/app/cache/static/weblate_fonts'". The tmpfs at
/app/cache/static sits inside the shared weblate_cache volume; the first
task's runc creates the mountpoint as root 0755, and runc gives a tmpfs
on an existing mountpoint that directory's mode. 99a7d28067 declared
mode 1777, which compose honours, but docker stack deploy keeps only the
tmpfs size (docker/cli 29.9.2 convert/volume.go), so swarm still got a
root 0755 tmpfs.
- utils/storage/nfs.py: nested_tmpfs_mountpoints() derives every tmpfs
mountpoint whose innermost enclosing volume of the same service is
NFS-backed, with its declared mode; a nested tmpfs without a mode, or
one mountpoint declared with two modes, raises.
- plugins/lookup/nfs_tmpfs_mountpoints.py feeds it the rendered compose
volumes, so compose_volumes stays the source of which volumes live on
NFS.
- sys-svc-compose nfs_prep.yml creates those mountpoints on the NFS
server with the declared mode before every stack deploy, which also
repairs a mountpoint an earlier deploy left at 0755.
- web-app-weblate: the cache volume declares nfs uid 1000, gid 0,
mode 0770; a pre-created mountpoint makes the volume non-empty, so
Docker no longer copies the image's /app/cache root in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.storage.nfs import nested_tmpfs_mountpoints, volume_dir
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
"""Tmpfs mountpoints in NFS volumes: {{ lookup('nfs_tmpfs_mountpoints', ...) }}."""
|
|
|
|
def run(
|
|
self,
|
|
terms: list[Any] | None,
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[list[dict[str, str]]]:
|
|
"""Return one list of ``{"path", "mode"}`` items, ``mode`` as an octal string.
|
|
|
|
Args:
|
|
terms: none.
|
|
variables: the templating context; ``application_id`` is read from it.
|
|
kwargs: ``volumes``, the rendered top-level compose ``volumes:`` mapping.
|
|
"""
|
|
if terms:
|
|
raise AnsibleError("nfs_tmpfs_mountpoints lookup expects no terms")
|
|
if "volumes" not in kwargs:
|
|
raise AnsibleError("nfs_tmpfs_mountpoints lookup requires 'volumes'")
|
|
volumes = kwargs["volumes"]
|
|
if not isinstance(volumes, Mapping):
|
|
raise AnsibleError(
|
|
"nfs_tmpfs_mountpoints: 'volumes' must be the compose volumes "
|
|
f"mapping, got {type(volumes).__name__}"
|
|
)
|
|
|
|
vars_ = variables or self._templar.available_variables
|
|
application_id = str(self._templar.template(vars_["application_id"])).strip()
|
|
applications = lookup_loader.get(
|
|
"applications", loader=self._loader, templar=self._templar
|
|
).run([], variables=vars_)[0]
|
|
if application_id not in applications:
|
|
raise AnsibleError(
|
|
f"nfs_tmpfs_mountpoints: unknown application_id '{application_id}'"
|
|
)
|
|
|
|
nfs_dirs = {
|
|
name: volume_dir(name, spec)
|
|
for name, spec in volumes.items()
|
|
if isinstance(spec, Mapping) and "driver_opts" in spec
|
|
}
|
|
try:
|
|
mountpoints = nested_tmpfs_mountpoints(
|
|
applications[application_id].get("volumes"), nfs_dirs
|
|
)
|
|
except (TypeError, ValueError) as exc:
|
|
raise AnsibleError(
|
|
f"nfs_tmpfs_mountpoints: {application_id}: {exc}"
|
|
) from exc
|
|
return [
|
|
[
|
|
{"path": mountpoint["path"], "mode": f"0{mountpoint['mode']:o}"}
|
|
for mountpoint in mountpoints
|
|
]
|
|
]
|