mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-03 19:32:08 +00:00
fix(oci): update and modify apps that mount a host system path (#531)
This commit is contained in:
@@ -354,12 +354,28 @@ def is_file_bind(mount, files=()):
|
||||
return mount['type'] == 'host-bind' and (mount['source'] in files or Path(mount['source']).is_file())
|
||||
|
||||
|
||||
# The host paths the installer mounts as the recipe gives them, never asking.
|
||||
SYSTEM_BIND_PREFIXES = ('/dev/', '/proc/', '/sys/', '/run/', '/var/run/')
|
||||
|
||||
|
||||
def declared_sources(contract):
|
||||
"""The host paths the recipe of a host monitor mounts on its own."""
|
||||
if not contract['deployment'].get('host_monitor'):
|
||||
return set()
|
||||
profile = contract['template'].get('proxmox', {}).get('installer_profile', {})
|
||||
return {item['source'] for item in profile.get('host_monitor_mounts', [])}
|
||||
"""The host paths the recipe mounts on its own: those of a host monitor,
|
||||
and the system paths of its volumes, such as /run/dbus."""
|
||||
template = contract.get('template') or {}
|
||||
profile = template.get('proxmox', {}).get('installer_profile', {})
|
||||
declared = set()
|
||||
if contract['deployment'].get('host_monitor'):
|
||||
declared |= {item['source'] for item in profile.get('host_monitor_mounts', [])}
|
||||
system = {}
|
||||
for volume in template.get('container_contract', {}).get('volumes', []):
|
||||
source = str(volume.get('compose_source_example') or volume.get('container_path') or '')
|
||||
if 'host-bind' in volume.get('installation_choice', []) and (
|
||||
source.startswith(SYSTEM_BIND_PREFIXES)
|
||||
or str(volume.get('container_path', '')).startswith(SYSTEM_BIND_PREFIXES)):
|
||||
system[volume['container_path']] = source
|
||||
declared |= {mount['source'] for mount in contract['deployment'].get('mounts', [])
|
||||
if mount['type'] == 'host-bind' and system.get(mount['container_path']) == mount['source']}
|
||||
return declared
|
||||
|
||||
|
||||
def profile_keys(deployment):
|
||||
|
||||
@@ -365,7 +365,8 @@ def apply(root, record, candidate, config, acknowledge_external_data=False):
|
||||
Path(mount['source']).mkdir(parents=True, exist_ok=True)
|
||||
host_mounts.verify_sources(original_sources)
|
||||
# Capture newly-created directories before installing their mounts.
|
||||
pinned = {p: host_mounts.validate_source(p) for p in desired_sources}
|
||||
pinned = {p: host_mounts.validate_source(p, declared=value.get('declared', False))
|
||||
for p, value in desired_sources.items()}
|
||||
for path, previous in desired_sources.items():
|
||||
if previous['exists'] and not host_mounts.same_source(previous, pinned[path]):
|
||||
raise ValueError(translate('A shared source does not match its recorded identity'))
|
||||
|
||||
@@ -104,6 +104,29 @@ class DeclaredSourceTests(TestCase):
|
||||
original, desired = transaction.freeze_host_sources(record, record, False)
|
||||
self.assertTrue(original['/usr']['declared'] and desired['/usr']['declared'])
|
||||
|
||||
def test_the_system_paths_of_a_recipe_are_declared(self):
|
||||
for app, path in (('homeassistant-official', '/run/dbus'), ('roonserver', '/run/udev')):
|
||||
template = json.loads((ROOT / f'catalog/apps/{app}.json').read_text())
|
||||
deployment = {'mounts': [bind(path, path)]}
|
||||
self.assertEqual(transaction.declared_sources({'template': template, 'deployment': deployment}), {path}, app)
|
||||
moved = {'mounts': [bind('/run/other', path)]}
|
||||
self.assertEqual(transaction.declared_sources({'template': template, 'deployment': moved}), set(), app)
|
||||
|
||||
def test_a_home_assistant_update_keeps_its_dbus_mount(self):
|
||||
template = json.loads((ROOT / 'catalog/apps/homeassistant-official.json').read_text())
|
||||
record = {'template': template, 'observed': {},
|
||||
'deployment': {'mounts': [bind('/run', '/run/dbus')]}}
|
||||
with self.assertRaises(ValueError):
|
||||
transaction.freeze_host_sources(record, record, True)
|
||||
template = json.loads(json.dumps(template))
|
||||
for volume in template['container_contract']['volumes']:
|
||||
if volume['container_path'] == '/run/dbus':
|
||||
volume['compose_source_example'] = '/run'
|
||||
record['template'] = template
|
||||
with patch.object(transaction, 'log'):
|
||||
original, desired = transaction.freeze_host_sources(record, record, False)
|
||||
self.assertTrue(original['/run']['declared'] and desired['/run']['declared'])
|
||||
|
||||
|
||||
class IncludeTests(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user