diff --git a/dockerspawner/dockerspawner.py b/dockerspawner/dockerspawner.py index ea37769..0916e36 100644 --- a/dockerspawner/dockerspawner.py +++ b/dockerspawner/dockerspawner.py @@ -1448,12 +1448,15 @@ def _fmt(v): return self.format_volume_name(v, self) for k, v in volumes.items(): - m = mode + rw = mode + p = 'rprivate' if isinstance(v, dict): if "mode" in v: - m = v["mode"] + rw = v["mode"] + if "propagation" in v: + p = v["propagation"] v = v["bind"] - binds[_fmt(k)] = {"bind": _fmt(v), "mode": m} + binds[_fmt(k)] = {"bind": _fmt(v), "mode": rw, 'propagation': p} return binds def _render_templates(self, obj, ns=None): diff --git a/dockerspawner/systemuserspawner.py b/dockerspawner/systemuserspawner.py index 70a4dbe..ce72646 100644 --- a/dockerspawner/systemuserspawner.py +++ b/dockerspawner/systemuserspawner.py @@ -34,6 +34,21 @@ class SystemUserSpawner(DockerSpawner): ), ) + image_homedir_propagation = Unicode( + "rprivate", + config=True, + help=dedent( + """ + Mode for bind mount propagation. Possible values are + `rshared`,`shared`,`rprivate`, `private`, `rslave`, `slave`. + default is `rprivate`. + This is only interpreted by docker-py if patched (see: + https://github.com/jannefleischer/docker-py/commit/786d55de465e72d0bb4b318272a2d020e43ff54a + ) and run on linux - no support for Docker Desktop. + """ + ), + ) + run_as_root = Bool( False, config=True, @@ -117,6 +132,23 @@ def volume_mount_points(self): mount_points.append(self.homedir) return mount_points + @property + def homedirpropagation(self): + """ + Setting for the propagation mode for home dir. Wrong values are defaulted to `rprivate`. + """ + if self.image_homedir_propagation in ( + 'rshared', + 'shared', + 'rprivate', + 'private', + 'rslave', + 'slave', + ): + return self.image_homedir_propagation + else: + return 'rprivate' + @property def volume_binds(self): """ @@ -129,7 +161,11 @@ def volume_binds(self): } """ volumes = super().volume_binds - volumes[self.host_homedir] = {'bind': self.homedir, 'ro': False} + volumes[self.host_homedir] = { + 'bind': self.homedir, + 'ro': False, + 'propagation': self.homedirpropagation, + } return volumes def get_env(self): diff --git a/tests/volumes_test.py b/tests/volumes_test.py index e4f521c..9938851 100644 --- a/tests/volumes_test.py +++ b/tests/volumes_test.py @@ -14,13 +14,15 @@ def test_binds(monkeypatch): d.user = types.SimpleNamespace(name="xyz") d.volumes = {"a": "b", "c": {"bind": "d", "mode": "Z"}} assert d.volume_binds == { - "a": {"bind": "b", "mode": "rw"}, - "c": {"bind": "d", "mode": "Z"}, + "a": {"bind": "b", "mode": "rw", "propagation": 'rprivate'}, + "c": {"bind": "d", "mode": "Z", "propagation": 'rprivate'}, } d.volumes = {"a": "b", "c": "d", "e": "f"} assert d.volume_mount_points == ["b", "d", "f"] d.volumes = {"/nfs/{username}": {"bind": "/home/{username}", "mode": "z"}} - assert d.volume_binds == {"/nfs/xyz": {"bind": "/home/xyz", "mode": "z"}} + assert d.volume_binds == { + "/nfs/xyz": {"bind": "/home/xyz", "mode": "z", "propagation": 'rprivate'} + } assert d.volume_mount_points == ["/home/xyz"] @@ -35,7 +37,13 @@ def test_format(label_template, spawner): return "THIS IS A TEST" d.format_volume_name = test_format - assert d.volume_binds == {"THIS IS A TEST": {"bind": "THIS IS A TEST", "mode": "z"}} + assert d.volume_binds == { + "THIS IS A TEST": { + "bind": "THIS IS A TEST", + "mode": "z", + "propagation": 'rprivate', + } + } assert d.volume_mount_points == ["THIS IS A TEST"] @@ -46,7 +54,11 @@ def test_default_format_volume_name(monkeypatch): d.user = types.SimpleNamespace(name="user@email.com") d.volumes = {"data/{username}": {"bind": "/home/{raw_username}", "mode": "z"}} assert d.volume_binds == { - "data/user-40email-2ecom": {"bind": "/home/user@email.com", "mode": "z"} + "data/user-40email-2ecom": { + "bind": "/home/user@email.com", + "mode": "z", + "propagation": 'rprivate', + } } assert d.volume_mount_points == ["/home/user@email.com"] @@ -60,7 +72,11 @@ def test_escaped_format_volume_name(monkeypatch): d.volumes = {"data/{username}": {"bind": "/home/{username}", "mode": "z"}} d.format_volume_name = dockerspawner.volumenamingstrategy.escaped_format_volume_name assert d.volume_binds == { - "data/user-40email-2ecom": {"bind": "/home/user-40email-2ecom", "mode": "z"} + "data/user-40email-2ecom": { + "bind": "/home/user-40email-2ecom", + "mode": "z", + "propagation": 'rprivate', + } } assert d.volume_mount_points == ["/home/user-40email-2ecom"]