config: Allow overriding target path in dropin config component

- To be used when configuration has to change based on the package version.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Sunil Mohan Adapa 2025-07-17 13:18:28 -07:00
parent 38810e566b
commit cc0a02ad1c
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 25 additions and 29 deletions

View File

@ -108,14 +108,12 @@ class DropinConfigs(app_module.FollowerComponent):
return results return results
@staticmethod def get_target_path(self, path):
def get_target_path(path):
"""Return Path object for a target path.""" """Return Path object for a target path."""
target = pathlib.Path(DropinConfigs.ROOT) target = pathlib.Path(self.ROOT)
target /= DropinConfigs.DROPIN_CONFIG_ROOT.lstrip('/') target /= self.DROPIN_CONFIG_ROOT.lstrip('/')
return target / path.lstrip('/') return target / path.lstrip('/')
@staticmethod def get_etc_path(self, path):
def get_etc_path(path):
"""Return Path object for etc path.""" """Return Path object for etc path."""
return pathlib.Path(DropinConfigs.ROOT) / path.lstrip('/') return pathlib.Path(self.ROOT) / path.lstrip('/')

View File

@ -10,7 +10,7 @@ from plinth import module_loader
from plinth.actions import privileged from plinth.actions import privileged
def _assert_managed_dropin_config(app_id: str, path: str): def _get_managed_dropin_config(app_id: str, path: str):
"""Check that this is a path managed by the specified app.""" """Check that this is a path managed by the specified app."""
module_path = module_loader.get_module_import_path(app_id) module_path = module_loader.get_module_import_path(app_id)
module = importlib.import_module(module_path) module = importlib.import_module(module_path)
@ -25,7 +25,7 @@ def _assert_managed_dropin_config(app_id: str, path: str):
components = app.get_components_of_type(DropinConfigs) components = app.get_components_of_type(DropinConfigs)
for component in components: for component in components:
if path in component.etc_paths: if path in component.etc_paths:
return return component
raise AssertionError('Not a managed drop-in config') raise AssertionError('Not a managed drop-in config')
@ -37,10 +37,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool,
Optionally, drop the link if it is invalid. Optionally, drop the link if it is invalid.
""" """
_assert_managed_dropin_config(app_id, path) component = _get_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs etc_path = component.get_etc_path(path)
etc_path = DropinConfigs.get_etc_path(path) target = component.get_target_path(path)
target = DropinConfigs.get_target_path(path)
if etc_path.exists() or etc_path.is_symlink(): if etc_path.exists() or etc_path.is_symlink():
if (not copy_only and etc_path.is_symlink() if (not copy_only and etc_path.is_symlink()
and etc_path.readlink() == target): and etc_path.readlink() == target):
@ -59,10 +58,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool,
@privileged @privileged
def dropin_link(app_id: str, path: str, copy_only: bool): def dropin_link(app_id: str, path: str, copy_only: bool):
"""Create a symlink from /etc/ to /usr/share/freedombox/etc.""" """Create a symlink from /etc/ to /usr/share/freedombox/etc."""
_assert_managed_dropin_config(app_id, path) component = _get_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs target = component.get_target_path(path)
target = DropinConfigs.get_target_path(path) etc_path = component.get_etc_path(path)
etc_path = DropinConfigs.get_etc_path(path)
etc_path.parent.mkdir(parents=True, exist_ok=True) etc_path.parent.mkdir(parents=True, exist_ok=True)
if copy_only: if copy_only:
shutil.copyfile(target, etc_path) shutil.copyfile(target, etc_path)
@ -73,7 +71,6 @@ def dropin_link(app_id: str, path: str, copy_only: bool):
@privileged @privileged
def dropin_unlink(app_id: str, path: str, missing_ok: bool = False): def dropin_unlink(app_id: str, path: str, missing_ok: bool = False):
"""Remove a symlink in /etc/.""" """Remove a symlink in /etc/."""
_assert_managed_dropin_config(app_id, path) component = _get_managed_dropin_config(app_id, path)
from plinth.config import DropinConfigs etc_path = component.get_etc_path(path)
etc_path = DropinConfigs.get_etc_path(path)
etc_path.unlink(missing_ok=missing_ok) etc_path.unlink(missing_ok=missing_ok)

View File

@ -31,9 +31,10 @@ def fixture_dropin_configs():
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def fixture_assert_dropin_config(): def fixture_assert_dropin_config(dropin_configs):
"""Mock asserting dropin config path.""" """Mock asserting dropin config path."""
with patch('plinth.privileged.config._assert_managed_dropin_config'): with patch('plinth.privileged.config._get_managed_dropin_config') as mock:
mock.return_value = dropin_configs
yield yield
@ -95,7 +96,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path):
# Enable when a file already exists # Enable when a file already exists
dropin_configs.disable() dropin_configs.disable()
etc_path = DropinConfigs.get_etc_path('/etc/test/path1') etc_path = dropin_configs.get_etc_path('/etc/test/path1')
etc_path.touch() etc_path.touch()
dropin_configs.enable() dropin_configs.enable()
_assert_symlinks(dropin_configs, tmp_path, should_exist=True) _assert_symlinks(dropin_configs, tmp_path, should_exist=True)
@ -108,7 +109,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path):
# When symlink already exists to correct location # When symlink already exists to correct location
dropin_configs.disable() dropin_configs.disable()
target_path = DropinConfigs.get_target_path('/etc/test/path1') target_path = dropin_configs.get_target_path('/etc/test/path1')
etc_path.symlink_to(target_path) etc_path.symlink_to(target_path)
dropin_configs.enable() dropin_configs.enable()
_assert_symlinks(dropin_configs, tmp_path, should_exist=True) _assert_symlinks(dropin_configs, tmp_path, should_exist=True)
@ -119,7 +120,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path):
with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path): with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path):
dropin_configs.copy_only = True dropin_configs.copy_only = True
for path in ['/etc/test/path1', '/etc/path2']: for path in ['/etc/test/path1', '/etc/path2']:
target = DropinConfigs.get_target_path(path) target = dropin_configs.get_target_path(path)
target.parent.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True)
target.write_text('test-config-content') target.write_text('test-config-content')
@ -135,7 +136,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path):
# Enable when a file already exists with wrong content # Enable when a file already exists with wrong content
dropin_configs.disable() dropin_configs.disable()
etc_path = DropinConfigs.get_etc_path('/etc/test/path1') etc_path = dropin_configs.get_etc_path('/etc/test/path1')
etc_path.write_text('x-invalid-content') etc_path.write_text('x-invalid-content')
dropin_configs.enable() dropin_configs.enable()
_assert_symlinks(dropin_configs, tmp_path, should_exist=True, _assert_symlinks(dropin_configs, tmp_path, should_exist=True,
@ -182,7 +183,7 @@ def test_dropin_config_diagnose_symlinks(dropin_configs, tmp_path):
# A file exists instead of symlink # A file exists instead of symlink
dropin_configs.disable() dropin_configs.disable()
etc_path = DropinConfigs.get_etc_path('/etc/test/path1') etc_path = dropin_configs.get_etc_path('/etc/test/path1')
etc_path.touch() etc_path.touch()
results = dropin_configs.diagnose() results = dropin_configs.diagnose()
assert results[0].result == 'failed' assert results[0].result == 'failed'
@ -204,7 +205,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path):
with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path): with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path):
dropin_configs.copy_only = True dropin_configs.copy_only = True
for path in ['/etc/test/path1', '/etc/path2']: for path in ['/etc/test/path1', '/etc/path2']:
target = DropinConfigs.get_target_path(path) target = dropin_configs.get_target_path(path)
target.parent.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True)
target.write_text('test-config-content') target.write_text('test-config-content')
@ -221,7 +222,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path):
# A symlink exists instead of a copied file # A symlink exists instead of a copied file
dropin_configs.disable() dropin_configs.disable()
etc_path = DropinConfigs.get_etc_path('/etc/test/path1') etc_path = dropin_configs.get_etc_path('/etc/test/path1')
etc_path.symlink_to('/blah') etc_path.symlink_to('/blah')
results = dropin_configs.diagnose() results = dropin_configs.diagnose()
assert results[0].result == 'failed' assert results[0].result == 'failed'