107 lines
5.3 KiB
Python
107 lines
5.3 KiB
Python
"""Run the real installer in a disposable root Linux filesystem fixture."""
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1]
|
|
MOCK = r'''#!/bin/sh
|
|
printf '%s %s\n' "$(basename "$0")" "$*" >> "$FIXTURE/commands"
|
|
case "$(basename "$0")" in
|
|
ssh-keygen) echo '256 SHA256:341ON+FsVZMVMsduFUZjevfxjagbFG9yPeZ0edTVSOk verified (ED25519)' ;;
|
|
logrotate)
|
|
if [ "${FAIL_LOGROTATE:-}" = yes ]; then
|
|
echo 'error: Ignoring writable configuration'
|
|
fi ;;
|
|
systemctl)
|
|
if [ "$1" = daemon-reload ] && [ "${FAIL_RELOAD:-}" = yes ]; then exit 1; fi
|
|
if [ "$1" = is-active ] && [ "${FAIL_SERVICES:-}" = yes ]; then exit 3; fi ;;
|
|
esac
|
|
'''
|
|
|
|
|
|
@unittest.skipUnless(os.name == 'posix' and getattr(os, 'geteuid', lambda: -1)() == 0,
|
|
'Disposable root Linux container required')
|
|
class ToolingInstallerTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory(prefix='eusrr-installer-test-')
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.source = self.root/'source'
|
|
self.source.mkdir()
|
|
for path in SOURCE.iterdir():
|
|
if path.is_file():
|
|
shutil.copyfile(path, self.source/path.name)
|
|
shutil.copytree(SOURCE/'systemd', self.source/'systemd')
|
|
# The actual deploy state machine is exercised in test_deploy_lifecycle.
|
|
# Here only its installer self-test exit outcome is controlled.
|
|
(self.source/'eusrr-deploy').write_text('#!/bin/sh\n[ "${FAIL_SELF_TEST:-}" != yes ]\n')
|
|
script = (SOURCE/'install-deploy-hardening').read_text()
|
|
for prefix in ('/srv/', '/var/', '/run/', '/etc/', '/usr/local/'):
|
|
script = script.replace(prefix, str(self.root)+prefix)
|
|
self.installer = self.source/'install-deploy-hardening'
|
|
self.installer.write_text(script)
|
|
self.bin = self.root/'bin'
|
|
self.bin.mkdir()
|
|
for command in ('systemctl', 'logrotate', 'visudo', 'ssh-keygen'):
|
|
path = self.bin/command
|
|
path.write_text(MOCK)
|
|
path.chmod(0o755)
|
|
for directory in ('srv/eusrr-slots', 'var/lib/eusrr-deploy', 'run/lock',
|
|
'etc/logrotate.d', 'etc/sudoers.d', 'etc/eusrr',
|
|
'etc/systemd/system', 'usr/local/sbin'):
|
|
(self.root/directory).mkdir(parents=True, exist_ok=True)
|
|
(self.root/'srv/eusrr-slots').chmod(0o2775)
|
|
(self.root/'var/lib/eusrr-deploy/active-slot').write_text('blue\n')
|
|
self.old_script = self.root/'usr/local/sbin/eusrr-deploy'
|
|
self.old_script.write_text('original deploy script\n')
|
|
self.old_script.chmod(0o755)
|
|
self.old_unit = self.root/'etc/systemd/system/eusrr-backend@.service'
|
|
self.old_unit.write_text('original backend unit\n')
|
|
self.env = {**os.environ, 'FIXTURE': str(self.root),
|
|
'PATH': str(self.bin)+':'+os.environ['PATH']}
|
|
|
|
def run_installer(self, **env):
|
|
result = subprocess.run(['bash', str(self.installer)], capture_output=True,
|
|
text=True, env={**self.env, **env}, timeout=20)
|
|
self.output = result.stdout+result.stderr
|
|
return result
|
|
|
|
def test_success_installs_controls_without_restart_or_cleanup(self):
|
|
self.assertEqual(self.run_installer().returncode, 0, self.output)
|
|
self.assertEqual((self.root/'srv/eusrr-slots').stat().st_mode & 0o777, 0o755)
|
|
self.assertIn('KillSignal=SIGTERM', self.old_unit.read_text())
|
|
self.assertTrue((self.root/'usr/local/libexec/eusrr-deploy-healthcheck').exists())
|
|
self.assertIn('^[0-9a-f]{40}$', (self.root/'etc/sudoers.d/eusrr-deploy').read_text())
|
|
commands = (self.root/'commands').read_text()
|
|
self.assertNotIn('restart', commands)
|
|
self.assertNotIn('--maintenance', commands)
|
|
self.assertNotIn('--vacuum', commands)
|
|
|
|
def test_failures_restore_every_changed_file_and_slot_mode(self):
|
|
for flag in ('FAIL_SELF_TEST', 'FAIL_SERVICES'):
|
|
with self.subTest(flag=flag):
|
|
self.assertNotEqual(self.run_installer(**{flag: 'yes'}).returncode, 0)
|
|
self.assertEqual(self.old_script.read_text(), 'original deploy script\n', self.output)
|
|
self.assertEqual(self.old_unit.read_text(), 'original backend unit\n', self.output)
|
|
self.assertFalse((self.root/'usr/local/libexec/eusrr-deploy-healthcheck').exists())
|
|
self.assertFalse((self.root/'etc/sudoers.d/eusrr-deploy').exists())
|
|
self.assertEqual((self.root/'srv/eusrr-slots').stat().st_mode & 0o7777, 0o2775)
|
|
|
|
def test_logrotate_ignored_file_is_a_failure_before_mutation(self):
|
|
self.assertNotEqual(self.run_installer(FAIL_LOGROTATE='yes').returncode, 0)
|
|
self.assertEqual(self.old_script.read_text(), 'original deploy script\n')
|
|
self.assertFalse((self.root/'usr/local/libexec/eusrr-deploy-healthcheck').exists())
|
|
|
|
def test_failed_recovery_is_not_reported_as_restored(self):
|
|
self.assertNotEqual(self.run_installer(FAIL_RELOAD='yes').returncode, 0)
|
|
self.assertIn('tooling recovery incomplete', self.output)
|
|
self.assertNotIn('Previous tooling restored', self.output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|