diff --git a/plugins/filter/container_volumes.py b/plugins/filter/container_volumes.py index f3fe83bce3..26ef614571 100644 --- a/plugins/filter/container_volumes.py +++ b/plugins/filter/container_volumes.py @@ -135,8 +135,13 @@ def container_volumes( "type": "tmpfs", "target": target, } + tmpfs_options: dict[str, Any] = {} if mount.get("size") is not None: - tmpfs_entry["tmpfs"] = {"size": mount["size"]} + tmpfs_options["size"] = mount["size"] + if mount.get("mode") is not None: + tmpfs_options["mode"] = _coerce_mode_int(mount["mode"]) + if tmpfs_options: + tmpfs_entry["tmpfs"] = tmpfs_options volumes_block.append(tmpfs_entry) elif vtype == "config": diff --git a/roles/web-app-checkmk/meta/services.yml b/roles/web-app-checkmk/meta/services.yml index d1bfc8f090..65c6de03ae 100644 --- a/roles/web-app-checkmk/meta/services.yml +++ b/roles/web-app-checkmk/meta/services.yml @@ -52,6 +52,7 @@ checkmk: enabled: true shared: true bond: 1 + site: cmk healthcheck: flavor: curl path: cmk/check_mk/login.py diff --git a/roles/web-app-checkmk/meta/volumes.yml b/roles/web-app-checkmk/meta/volumes.yml index 9037c1d647..52c937bb90 100644 --- a/roles/web-app-checkmk/meta/volumes.yml +++ b/roles/web-app-checkmk/meta/volumes.yml @@ -5,3 +5,11 @@ checkmk_data: mounts: - service: checkmk target: /omd/sites + +site_tmp: + type: tmpfs + mounts: + - service: checkmk + target: "/omd/sites/{{ lookup('config', 'web-app-checkmk', 'services.checkmk.site') }}/tmp" + size: 512m + mode: "01777" diff --git a/roles/web-app-checkmk/vars/main.yml b/roles/web-app-checkmk/vars/main.yml index 979d5c01b0..9808167fd4 100644 --- a/roles/web-app-checkmk/vars/main.yml +++ b/roles/web-app-checkmk/vars/main.yml @@ -3,7 +3,7 @@ application_id: "web-app-checkmk" container_port: "{{ lookup('config', application_id, 'services.checkmk.ports.internal.http') }}" CHECKMK_NAME: "{{ lookup('config', application_id, 'services.checkmk.name') }}" -CHECKMK_SITE: "cmk" +CHECKMK_SITE: "{{ lookup('config', application_id, 'services.checkmk.site') }}" CHECKMK_ADMIN_PASSWORD: "{{ lookup('config', application_id, 'credentials.cmk_password') }}" diff --git a/tests/unit/plugins/filter/test_container_volumes.py b/tests/unit/plugins/filter/test_container_volumes.py index 259f938c9d..3728ffe801 100644 --- a/tests/unit/plugins/filter/test_container_volumes.py +++ b/tests/unit/plugins/filter/test_container_volumes.py @@ -144,6 +144,30 @@ class TestContainerVolumesTmpfs(unittest.TestCase): }, ) + def test_tmpfs_mode_renders_as_decimal_of_the_octal_string(self) -> None: + meta = { + "scratch": { + "type": "tmpfs", + "mounts": [ + {"service": "app", "target": "/scratch", "mode": "01777"}, + ], + } + } + out = container_volumes(_apps(meta), "my-app", "app") + data = _parse(out) + self.assertEqual( + data, + { + "volumes": [ + { + "type": "tmpfs", + "target": "/scratch", + "tmpfs": {"mode": 0o1777}, + } + ] + }, + ) + class TestContainerVolumesServiceFilter(unittest.TestCase): def test_only_emits_mounts_for_requested_service(self) -> None: diff --git a/utils/roles/applications/mounts.py b/utils/roles/applications/mounts.py index dcb6b46182..9a64180f6e 100644 --- a/utils/roles/applications/mounts.py +++ b/utils/roles/applications/mounts.py @@ -22,6 +22,14 @@ Canonical shape (dict-of-dicts; the YAML key is the semantic short name): - service: synapse target: /etc/synapse/config + scratch: + type: tmpfs + mounts: + - service: synapse + target: /var/run/synapse + size: 64m # per-mount tmpfs size + mode: "01777" # per-mount tmpfs permission bits + The legacy list-of-dicts shape is no longer accepted. """ @@ -45,6 +53,13 @@ class VolumesSchemaError(ValueError): """Raised when a meta/volumes.yml fails schema validation.""" +def _is_octal_mode(value: Any) -> bool: + """True when ``value`` is a leading-zero octal permission string.""" + return ( + isinstance(value, str) and value.startswith("0") and value.lstrip("0").isdigit() + ) + + def normalize_volumes_meta( meta_volumes: Any, ) -> dict[str, dict[str, Any]]: @@ -120,16 +135,15 @@ def validate_volumes_meta( violations.append( f"{prefix}: 'mode' is only valid for type config/secret (got type={vtype!r})" ) - if "mode" in entry and vtype in _TYPE_HAS_MODE: - mode = entry["mode"] - if not ( - isinstance(mode, str) - and mode.startswith("0") - and mode.lstrip("0").isdigit() - ): - violations.append( - f"{prefix}: 'mode' must be an octal string like \"0440\", got {mode!r}" - ) + if ( + "mode" in entry + and vtype in _TYPE_HAS_MODE + and not _is_octal_mode(entry["mode"]) + ): + violations.append( + f"{prefix}: 'mode' must be an octal string like \"0440\", " + f"got {entry['mode']!r}" + ) if "read_only" in entry and vtype not in _TYPE_HAS_READ_ONLY: violations.append( @@ -176,6 +190,12 @@ def validate_volumes_meta( for mount in (entry.get("mounts") or []) if "size" in mount and not isinstance(mount["size"], (str, int)) ) + violations.extend( + f"{prefix}: tmpfs 'mode' must be an octal string like \"01777\", " + f"got {mount['mode']!r}" + for mount in (entry.get("mounts") or []) + if "mode" in mount and not _is_octal_mode(mount["mode"]) + ) mounts = entry.get("mounts") if mounts is None: