fix(oci): normalize read-only time mounts in custom stacks

This commit is contained in:
Jonatan
2026-10-28 06:41:20 +01:00
parent 68ae23df82
commit b383fb9311
2 changed files with 11 additions and 4 deletions
+3 -4
View File
@@ -174,7 +174,7 @@ def template_from_compose(text: str, title: str | None = None) -> dict[str, Any]
def _template_from_stack(compose: dict[str, Any], title: str | None) -> dict[str, Any]: def _template_from_stack(compose: dict[str, Any], title: str | None) -> dict[str, Any]:
"""Reuse the catalog stack compiler, keeping explicitly selected image versions.""" """Reuse the catalog stack compiler, keeping explicitly selected image versions."""
from .stack import apply_stack_support from .stack import apply_stack_support, normalized_mounts
services = _services(compose) services = _services(compose)
singles = {} singles = {}
@@ -210,9 +210,8 @@ def _template_from_stack(compose: dict[str, Any], title: str | None) -> dict[str
if 'environment' in services[service['name']]: if 'environment' in services[service['name']]:
service['compose']['environment'] = copy.deepcopy(services[service['name']]['environment']) service['compose']['environment'] = copy.deepcopy(services[service['name']]['environment'])
service['compose']['volumes'] = [ service['compose']['volumes'] = [
mount for mount in service['compose'].get('volumes', []) mount for mount in normalized_mounts(service['compose'].get('volumes', []))
if (mount.get('target') if isinstance(mount, dict) if mount['target'] not in TIME_SETTINGS]
else str(mount).split(':')[-1]) not in TIME_SETTINGS]
template['container_contract']['original_compose'] = _dump({ template['container_contract']['original_compose'] = _dump({
**template['compose_stack']['top_level'], **template['compose_stack']['top_level'],
'services': {service['name']: service['compose'] for service in template['compose_stack']['services']}}) 'services': {service['name']: service['compose'] for service in template['compose_stack']['services']}})
+8
View File
@@ -55,6 +55,14 @@ class CustomComposeStacks(unittest.TestCase):
with self.subTest(metadata=metadata), self.assertRaises(ConversionError): with self.subTest(metadata=metadata), self.assertRaises(ConversionError):
custom.template_from_compose(COMPOSE + metadata + '\n') custom.template_from_compose(COMPOSE + metadata + '\n')
def test_short_read_only_time_mount_is_removed_from_stack(self):
text = COMPOSE.replace(" ports:", " volumes: [/etc/localtime:/etc/localtime:ro]\n ports:")
template = custom.template_from_compose(text)
app = next(service for service in ordered_services(template) if service['name'] == 'app')
self.assertEqual(app['compose']['volumes'], [])
self.assertFalse(any('System path' in error for error in template['compatibility']['untranslated_blockers']))
self.assertTrue(template['compatibility']['automatic_install_candidate'])
def test_written_digest_and_tag_survive_in_full_image_contracts(self): def test_written_digest_and_tag_survive_in_full_image_contracts(self):
digest = 'sha256:' + 'a' * 64 digest = 'sha256:' + 'a' * 64
reference = 'ghcr.io/example/adminer:4@' + digest reference = 'ghcr.io/example/adminer:4@' + digest