mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
feat(oci): offer access to an existing host directory and gather the file-sharing applications
feat(oci): offer access to a host directory that already exists feat(oci): gather the file-sharing applications in Files & Sharing fix(oci): ShareCoveX keeps its settings in a 1 GB volume, and the new translations are mended
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
installation, and its privileged profile is offered by the advanced one only."""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -10,6 +12,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from proxmenux_oci import host
|
||||
from proxmenux_oci.catalog import Catalog
|
||||
from proxmenux_oci.installer import ADVANCED_MODE, DEFAULT_MODE, InstallError, build_deployment
|
||||
from proxmenux_oci.recreation import edit_recreation
|
||||
@@ -101,6 +104,10 @@ class SharedDirectoriesTests(unittest.TestCase):
|
||||
self.assertFalse(any("ShareCoveX panel administrator" in text for text in ui.asked))
|
||||
self.assertEqual([m["container_path"] for m in plan["mounts"]], ["/config", "/shares/media"])
|
||||
self.assertEqual(plan["mounts"][1]["size_gb"], 32)
|
||||
# The settings stay small: their volume is created at the size the policy states.
|
||||
self.assertEqual(plan["mounts"][0]["size_gb"], 1)
|
||||
other = self.catalog.compose("seerr")["container_contract"]["volumes"][0]["managed_volume"]
|
||||
self.assertGreaterEqual(other["default_size_gb"], 16)
|
||||
|
||||
def test_only_the_advanced_installation_offers_nfsv4_and_it_makes_the_container_privileged(self, *_):
|
||||
ui, _template, plan = self.build({NAME: ["media"]}, ADVANCED_MODE)
|
||||
@@ -125,6 +132,70 @@ class SharedDirectoriesTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(InstallError, "minimum number of characters is 8"):
|
||||
self.build({NAME: ["media"], "(empty = generate)": ["short"]}, ADVANCED_MODE)
|
||||
|
||||
def test_an_existing_directory_the_application_cannot_write_to_offers_access(self, *_):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
answers = {NAME: ["media"], "Where to store /shares/media": ["host-bind"],
|
||||
"Host path for /shares/media": [directory]}
|
||||
with patch("proxmenux_oci.installer.host.can_write", return_value=False) as check:
|
||||
ui, _template, plan = self.build(answers)
|
||||
question = next(text for text in ui.asked if "Grant ShareCoveX access" in text)
|
||||
# The identity is the one of the volumes, in the range of an unprivileged container.
|
||||
check.assert_called_once_with(directory, 101000, 101000)
|
||||
self.assertIn("UID 101000", question)
|
||||
self.assertIn("changes owner or permissions", question)
|
||||
self.assertIs(plan["mounts"][1]["grant_access"], True)
|
||||
_ui, _template, declined = self.build({**answers, question: [False]})
|
||||
self.assertNotIn("grant_access", declined["mounts"][1])
|
||||
with patch("proxmenux_oci.installer.host.can_write", return_value=True):
|
||||
ui, _template, plan = self.build(answers)
|
||||
self.assertFalse(any("Grant ShareCoveX access" in text for text in ui.asked))
|
||||
self.assertNotIn("grant_access", plan["mounts"][1])
|
||||
# A privileged container writes with the identity as it is.
|
||||
nfsv4 = "NFSv4 needs a privileged container"
|
||||
with patch("proxmenux_oci.installer.host.can_write", return_value=False) as check:
|
||||
ui = ScriptedUI(answers)
|
||||
template = self.catalog.compose("sharecovex")
|
||||
first = build_deployment(template, ui, ADVANCED_MODE)
|
||||
prompt = next(text for text in ui.asked if nfsv4 in text)
|
||||
check.reset_mock()
|
||||
build_deployment(self.catalog.compose("sharecovex"), ScriptedUI({**answers, prompt: [True]}),
|
||||
ADVANCED_MODE)
|
||||
check.assert_called_once_with(directory, 1000, 1000)
|
||||
self.assertIsNotNone(first)
|
||||
|
||||
def test_a_directory_that_does_not_exist_yet_is_created_for_the_application(self, *_):
|
||||
with patch("proxmenux_oci.installer.host.can_write") as check:
|
||||
ui, _template, plan = self.build({NAME: ["media"], "Where to store /shares/media": ["host-bind"],
|
||||
"Host path for /shares/media": ["/nonexistent/proxmenux-test/media"]})
|
||||
check.assert_not_called()
|
||||
self.assertNotIn("grant_access", plan["mounts"][1])
|
||||
self.assertIs(plan["mounts"][1]["create_if_missing"], True)
|
||||
|
||||
def test_write_access_is_read_from_the_mode_bits(self, *_):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
status = os.stat(directory)
|
||||
os.chmod(directory, 0o750)
|
||||
self.assertTrue(host.can_write(directory, status.st_uid, 99999))
|
||||
self.assertFalse(host.can_write(directory, 99998, status.st_gid))
|
||||
self.assertFalse(host.can_write(directory, 99998, 99999))
|
||||
os.chmod(directory, 0o770)
|
||||
self.assertTrue(host.can_write(directory, 99998, status.st_gid))
|
||||
os.chmod(directory, 0o757)
|
||||
self.assertTrue(host.can_write(directory, 99998, 99999))
|
||||
os.chmod(directory, 0o700)
|
||||
self.assertFalse(host.can_write("/nonexistent/proxmenux-test", 0, 0))
|
||||
|
||||
def test_the_installer_script_grants_access_only_when_asked(self, *_):
|
||||
script = (ROOT / "remote/install_oci.sh").read_text()
|
||||
self.assertIn("(.grant_access // false)] | @tsv", script)
|
||||
self.assertIn('if [[ -d $SOURCE && $GRANT_ACCESS == true ]]; then', script)
|
||||
# The owner is never changed, and the rule is not applied twice.
|
||||
grant = script[script.index("grant_shared_access() {"):script.index("MOUNT_ENTRIES=")]
|
||||
self.assertNotIn("chown", grant)
|
||||
self.assertNotIn("chmod", grant)
|
||||
self.assertIn('grep -q "^default:user:${HOST_BIND_UID}:rwx"', grant)
|
||||
self.assertIn('setfacl -R -P -m "u:${HOST_BIND_UID}:rwX,g:${HOST_BIND_GID}:rwX"', grant)
|
||||
|
||||
def test_a_name_that_cannot_identify_a_share_is_refused(self, *_):
|
||||
for name in ("Media", "my files", "9lives", "config", "", "a" * 33):
|
||||
with self.assertRaisesRegex(ValueError, "lowercase"):
|
||||
|
||||
Reference in New Issue
Block a user