feat(oci): Recreate for multi-container apps and CPU/memory in every advanced install

This commit is contained in:
MacRimi
2026-10-01 19:39:01 +02:00
parent d137bb3d31
commit a4cb9936a5
29 changed files with 1405 additions and 110 deletions
+104
View File
@@ -0,0 +1,104 @@
"""Every multi-container application offers extra paths in an advanced installation."""
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
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.catalog import Catalog
from proxmenux_oci.cli import _deployment_summary_text
from proxmenux_oci.installer import ADVANCED_MODE, DEFAULT_MODE, build_deployment
from test_advanced_flow_order import RecordingUI, addresses, storages
EXTRA = "Add an extra custom path"
SPECIAL = ("nextcloud-stack", "paperless-ngx", "tandoor", "immich")
class ExtraPathUI(RecordingUI):
"""Adds one container volume the first time the extra path is offered."""
def __init__(self):
super().__init__({"Path inside the container (e.g. /media-extra)": "/media-extra",
"Volume size in GB": "20"})
self.offered = 0
def confirm(self, text, default=False):
if text == EXTRA:
self.asked.append(text)
self.offered += 1
return self.offered == 1
return super().confirm(text, default)
@patch("proxmenux_oci.i18n.language", return_value="en")
@patch("proxmenux_oci.installer.host.storages", side_effect=storages)
@patch("proxmenux_oci.installer.host.bridges", return_value=[{"iface": "vmbr0", "cidr": "192.0.2.10/24"}])
@patch("proxmenux_oci.installer.host.timezone", return_value="Europe/Madrid")
@patch("proxmenux_oci.installer.access.ask_addresses", side_effect=addresses)
class StackExtraPathTests(unittest.TestCase):
catalog = Catalog(ROOT)
def test_the_advanced_installation_offers_an_extra_path_before_the_network(self, *_):
for app in SPECIAL:
ui = ExtraPathUI()
template = self.catalog.compose(app)
plan = build_deployment(template, ui, ADVANCED_MODE)
self.assertEqual(ui.asked.count(EXTRA), 2, app)
bridge = next(text for text in ui.asked if text.startswith("Access bridge"))
self.assertLess(ui.asked.index(EXTRA), ui.asked.index(bridge), app)
self.assertEqual([(m["type"], m["container_path"], m["size_gb"]) for m in plan["extra_mounts"]],
[("managed-volume", "/media-extra", 20)], app)
self.assertIn("/media-extra", _deployment_summary_text(template, plan), app)
def test_an_extra_path_cannot_hide_the_data_of_the_application(self, *_):
ui = ExtraPathUI()
ui.answers["Path inside the container (e.g. /media-extra)"] = "/var/www/html/data"
with self.assertRaises(ValueError):
build_deployment(self.catalog.compose("nextcloud-stack"), ui, ADVANCED_MODE)
def test_the_advanced_installation_offers_usb_devices_for_the_application(self, *_):
device = "Add another GPU or USB device manually?"
usb = [{"path": "/dev/ttyACM1", "name": "Z-Stick", "kind": "Communications"}]
for app in SPECIAL:
ui = RecordingUI()
offered = []
def confirm(text, default=False, ui=ui, offered=offered):
ui.asked.append(text)
if text == device:
offered.append(text)
return len(offered) == 1
return default
def choose(text, options, default=None, ui=ui):
ui.asked.append(text)
tags = [tag for tag, _label in options]
if text == "Device to attach":
self.assertNotIn("nvidia", tags, app)
return "usb"
return "/dev/ttyACM1" if "/dev/ttyACM1" in tags else default
ui.confirm, ui.choose = confirm, choose
template = self.catalog.compose(app)
with patch("proxmenux_oci.extra_devices.host.usb_devices", return_value=usb):
plan = build_deployment(template, ui, ADVANCED_MODE)
self.assertEqual([(d["kind"], d["host_path"], d["gid_strategy"]) for d in plan["extra_devices"]],
[("character-device", "/dev/ttyACM1", "host-device-gid")], app)
self.assertIn("/dev/ttyACM1", _deployment_summary_text(template, plan), app)
def test_the_default_installation_does_not_ask_it(self, *_):
for app in SPECIAL:
ui = RecordingUI()
plan = build_deployment(self.catalog.compose(app), ui, DEFAULT_MODE)
self.assertNotIn(EXTRA, ui.asked, app)
self.assertEqual(plan["extra_mounts"], [], app)
self.assertEqual(plan["extra_devices"], [], app)
self.assertNotIn("Add another GPU or USB device manually?", ui.asked, app)
if __name__ == "__main__":
unittest.main()