diff --git a/oci/src/proxmenux_oci/stack.py b/oci/src/proxmenux_oci/stack.py index b4d52ea2..1c383b44 100644 --- a/oci/src/proxmenux_oci/stack.py +++ b/oci/src/proxmenux_oci/stack.py @@ -523,6 +523,23 @@ class DefaultsUI: pass +def stack_lxc_hostname(stack_name, service_name, main=False): + """Give the main LXC the stack name and avoid repeating it in members.""" + from .installer import _hostname_default + + stack = _hostname_default(stack_name) + if main: + return stack + + suffix = _hostname_default(service_name) + prefix = stack + '-' + if suffix.startswith(prefix): + suffix = suffix[len(prefix):] + if suffix == 'database': + suffix = 'db' + return _hostname_default(f'{stack}-{suffix}') + + def _service_plan(template, service): """The single-container template and plan of one stack member.""" from .installer import build_deployment, DEFAULT_MODE @@ -677,7 +694,7 @@ def build_stack(template, ui, mode='advanced'): k = kind(s['image']) # Values are already resolved; do not reinterpret user credentials as Compose variables. plan['environment'] = [{'name':key,'value':value,'sensitive':True} for key,value in env.items() if value != '' or literal] - plan.update(hostname=_hostname_default(name+'-'+s['name']), template_storage=cache, + plan.update(hostname=stack_lxc_hostname(name, s['name'], main), template_storage=cache, start_after_create=False) plan['rootfs']['storage'] = root if 'memory_default_mb' not in single['proxmox'].get('installer_profile', {}).get('resources', {}): diff --git a/oci/tests/test_stack_lxc_hostnames.py b/oci/tests/test_stack_lxc_hostnames.py new file mode 100644 index 00000000..07c73443 --- /dev/null +++ b/oci/tests/test_stack_lxc_hostnames.py @@ -0,0 +1,47 @@ +"""Stack LXC names use the stack name once and readable dependency suffixes.""" +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")) + +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.installer import DEFAULT_MODE +from proxmenux_oci.stack import DefaultsUI, build_stack, stack_lxc_hostname + + +class StackLxcHostnameTests(unittest.TestCase): + def test_main_service_uses_stack_name_without_repeating_service_name(self): + self.assertEqual(stack_lxc_hostname("TeslaMate", "teslamate", True), "teslamate") + + def test_database_uses_db_suffix_and_other_services_keep_their_names(self): + self.assertEqual(stack_lxc_hostname("teslamate", "database"), "teslamate-db") + self.assertEqual(stack_lxc_hostname("teslamate", "grafana"), "teslamate-grafana") + self.assertEqual(stack_lxc_hostname("teslamate", "mosquitto"), "teslamate-mosquitto") + + def test_service_already_prefixed_with_stack_name_does_not_repeat_it(self): + self.assertEqual(stack_lxc_hostname("blinko", "blinko-postgres"), "blinko-postgres") + + def test_teslamate_build_plan_contains_the_expected_proxmox_names(self): + template = Catalog(ROOT).compose("teslamate") + with patch("proxmenux_oci.host.default_storage", return_value="local-zfs"), \ + patch("proxmenux_oci.host.default_bridge", return_value="vmbr0"), \ + patch("proxmenux_oci.network.ask_addresses", + side_effect=lambda ui, bridge, labels: ({label: "dhcp" for label in labels}, None)), \ + patch("proxmenux_oci.host.timezone", return_value="UTC"): + result = build_stack(template, DefaultsUI(), DEFAULT_MODE) + + actual = {service["name"]: service["deployment"]["hostname"] + for service in result["services"]} + self.assertEqual(actual, { + "database": "teslamate-db", + "grafana": "teslamate-grafana", + "mosquitto": "teslamate-mosquitto", + "teslamate": "teslamate", + }) + + +if __name__ == "__main__": + unittest.main()