Keep systemd-managed directories inside root snapshots

This commit is contained in:
Codex
2026-10-13 23:03:31 +03:00
parent 6058a0660e
commit 579f3bb071
2 changed files with 109 additions and 0 deletions
+72
View File
@@ -23,6 +23,17 @@ class ImageTests(unittest.TestCase):
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name) / 'rootfs'
self.root.mkdir()
self.vendor_tmpfiles = {
'home.conf': '# vendor home policy\nQ /home 0755 - - -\nq /srv 0755 - - -\n',
'portables.conf': '# vendor portables policy\nQ /var/lib/portables 0700\n',
'systemd-nspawn.conf': (
'# vendor nspawn policy\nQ /var/lib/machines 0700 - - -\n'
'R! /var/lib/machines/.#*\nR! /.#machine.*\n'
'L? /run/systemd/io.systemd.NamespaceResource - - - - /run/host/io.systemd.NamespaceResource\n'
'L? /run/systemd/io.systemd.MountFileSystem - - - - /run/host/io.systemd.MountFileSystem\n'),
}
for name, content in self.vendor_tmpfiles.items():
self.file('usr/lib/tmpfiles.d/' + name, content)
def file(self, relative, content='preserved'):
path = self.root / relative
@@ -35,6 +46,67 @@ class ImageTests(unittest.TestCase):
self.file('boot/vmlinuz-' + version, 'kernel')
return version
def test_managed_root_tmpfiles_creates_ordinary_directories_without_losing_vendor_rules(self):
self.kernel()
source = Path(__file__).resolve().parents[1]
retained = [self.file(path + '/retained-data', 'user data')
for path in ('srv', 'var/lib/portables', 'var/lib/machines')]
image.configure_rootfs(self.root, source)
expected = {
'home.conf': '# vendor home policy\nQ /home 0755 - - -\nd /srv 0755 - - -\n',
'portables.conf': '# vendor portables policy\nd /var/lib/portables 0700\n',
'systemd-nspawn.conf': (
'# vendor nspawn policy\nd /var/lib/machines 0700 - - -\n'
'R! /var/lib/machines/.#*\nR! /.#machine.*\n'
'L? /run/systemd/io.systemd.NamespaceResource - - - - /run/host/io.systemd.NamespaceResource\n'
'L? /run/systemd/io.systemd.MountFileSystem - - - - /run/host/io.systemd.MountFileSystem\n'),
}
for name, content in expected.items():
with self.subTest(name=name):
override = self.root / 'etc/tmpfiles.d' / name
self.assertTrue(override.is_file(), f'missing managed-root policy: {name}')
self.assertEqual(override.read_text(), content)
self.assertEqual(override.stat().st_mode & 0o777, 0o644)
self.assertEqual((self.root / 'usr/lib/tmpfiles.d' / name).read_text(),
self.vendor_tmpfiles[name])
self.assertEqual([path.read_text() for path in retained], ['user data'] * 3)
def test_managed_root_tmpfiles_refuses_missing_duplicate_or_changed_vendor_rule(self):
import shutil
self.kernel()
source = Path(__file__).resolve().parents[1]
for index, content in enumerate((None, '# no managed path\n', 'd /var/lib/machines 0700\n',
'Q /var/lib/machines 0700\nq /var/lib/machines 0700\n',
'Q /var/lib/machines 0700\nd /var/lib/machines 0700\n')):
with self.subTest(content=content):
root = Path(self.tmp.name) / ('drift-' + str(index))
shutil.copytree(self.root, root)
vendor = root / 'usr/lib/tmpfiles.d/systemd-nspawn.conf'
if content is None:
vendor.unlink()
else:
vendor.write_text(content)
with self.assertRaisesRegex(image.ImageError, 'tmpfiles'):
image.configure_rootfs(root, source)
self.assertFalse((root / 'etc/tmpfiles.d/home.conf').exists())
def test_managed_root_tmpfiles_refuses_existing_override(self):
self.kernel()
original = self.file('etc/tmpfiles.d/home.conf', 'd /srv 0710 local local -\n')
with self.assertRaisesRegex(image.ImageError, 'tmpfiles'):
image.configure_rootfs(self.root, Path(__file__).resolve().parents[1])
self.assertEqual(original.read_text(), 'd /srv 0710 local local -\n')
def test_managed_root_tmpfiles_refuses_vendor_symlink(self):
self.kernel()
original = self.file('usr/lib/tmpfiles.d/other.conf', 'Q /var/lib/machines 0700\n')
vendor = self.root / 'usr/lib/tmpfiles.d/systemd-nspawn.conf'
vendor.unlink()
vendor.symlink_to('other.conf')
with self.assertRaisesRegex(image.ImageError, 'tmpfiles'):
image.configure_rootfs(self.root, Path(__file__).resolve().parents[1])
self.assertEqual(original.read_text(), 'Q /var/lib/machines 0700\n')
def test_installs_independent_updater_and_base_recovery_assets(self):
self.kernel()
source = Path(__file__).resolve().parents[1]