Fix Docker client installation on Debian 13

This commit is contained in:
2026-10-04 19:14:36 -04:00
parent fae7775c72
commit 1392a082da
4 changed files with 137 additions and 8 deletions
+104
View File
@@ -20,6 +20,7 @@ PREFLIGHT = next(block for block in BLOCKS if "# INSTALLATION_PREFLIGHT:" in blo
OWNERSHIP = next(block for block in BLOCKS if "# INSTALLATION_OWNERSHIP:" in block)
RELEASE = next(block for block in BLOCKS if "# INSTALLATION_RELEASE:" in block)
CLEANUP = re.search(r"installation_cleanup\(\) \{.*?\n\}", SOURCE, re.DOTALL).group()
DOCKER = SOURCE[SOURCE.index("# INSTALLATION_DOCKER:"):SOURCE.index("install -d -m 0700 /etc/electric-cherry")]
SPEC = importlib.util.spec_from_file_location("package_release", Path(__file__).resolve().parents[1] / "package_release.py")
PACKAGER = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PACKAGER)
@@ -230,6 +231,109 @@ class InstallerTests(unittest.TestCase):
self.preflight()
class DockerInstallerTests(unittest.TestCase):
def run_docker_setup(self, version="13", cli=False, engine=False, available=True,
data=False, config=False, apt_status=0, supply_cli=True):
with tempfile.TemporaryDirectory() as temporary:
base = Path(temporary)
docker_data = base / "docker-data"
docker_config = base / "daemon.json"
if data:
docker_data.mkdir()
(docker_data / "sentinel").write_text("existing containers")
if config:
docker_config.write_text("existing configuration")
block = DOCKER.replace("/var/lib/docker", str(docker_data)).replace("/etc/docker/daemon.json", str(docker_config))
# Simulate Debian's package split and Docker state. All commands that
# could install packages or touch services are shell stubs.
script = r'''
set -euo pipefail
command() { [ "$*" = '-v docker' ] && [ "$EC_TEST_CLI" = true ]; }
dpkg-query() {
[ "$*" = '-W -f=${Status} docker.io' ] || exit 90
[ "$EC_TEST_ENGINE" = true ] || return 1
printf 'install ok installed'
}
apt-get() {
printf 'apt-get %s\n' "$*"
[ "$EC_TEST_APT_STATUS" = 0 ] || return "$EC_TEST_APT_STATUS"
if [ "$EC_TEST_SUPPLY_CLI" = true ]; then
case " $* " in
*' docker-cli '*) EC_TEST_CLI=true ;;
*' docker.io '*) if [ "$VERSION_ID" = 12 ]; then EC_TEST_CLI=true; fi ;;
esac
fi
}
systemctl() { printf 'systemctl %s\n' "$*"; }
docker() {
[ "$EC_TEST_CLI" = true ] || return 127
[ "$*" = info ] || exit 91
if [ "$EC_TEST_AVAILABLE" != true ]; then
echo 'Cannot connect to the Docker daemon' >&2
return 1
fi
}
'''
environment = {**os.environ, "VERSION_ID": version,
"EC_TEST_CLI": str(cli).lower(), "EC_TEST_ENGINE": str(engine).lower(),
"EC_TEST_AVAILABLE": str(available).lower(), "EC_TEST_APT_STATUS": str(apt_status),
"EC_TEST_SUPPLY_CLI": str(supply_cli).lower()}
result = subprocess.run(["bash", "-c", script + block], env=environment, capture_output=True, text=True)
if data:
self.assertEqual((docker_data / "sentinel").read_text(), "existing containers")
if config:
self.assertEqual(docker_config.read_text(), "existing configuration")
return result
def test_fresh_debian_12_and_13_install_both_engine_and_client(self):
for version, packages in (("12", "docker.io"), ("13", "docker.io docker-cli")):
with self.subTest(version=version):
result = self.run_docker_setup(version=version)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.splitlines(), [
f"apt-get install -y --no-install-recommends {packages}",
"systemctl enable --now docker"])
def test_rerun_repairs_debian_13_missing_client_without_touching_engine(self):
result = self.run_docker_setup(engine=True, data=True, config=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.splitlines(), [
"apt-get install -y --no-install-recommends --no-remove docker-cli"])
def test_existing_docker_including_ce_is_reused_without_package_or_service_changes(self):
for engine in (False, True):
with self.subTest(debian_engine=engine):
result = self.run_docker_setup(cli=True, engine=engine, data=True, config=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout, "")
def test_unknown_existing_data_or_configuration_still_blocks_engine_installation(self):
for data, config in ((True, False), (False, True)):
with self.subTest(data=data, config=config):
result = self.run_docker_setup(data=data, config=config)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Existing Docker data/configuration", result.stderr)
self.assertEqual(result.stdout, "")
def test_unavailable_existing_engine_exposes_docker_error_without_restarting_it(self):
result = self.run_docker_setup(cli=True, available=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Cannot connect to the Docker daemon", result.stderr)
self.assertIn("journalctl -u docker", result.stderr)
self.assertEqual(result.stdout, "")
def test_package_failure_stops_before_starting_docker(self):
result = self.run_docker_setup(apt_status=42)
self.assertEqual(result.returncode, 42)
self.assertNotIn("systemctl", result.stdout)
def test_missing_client_is_distinguished_from_an_unavailable_daemon(self):
result = self.run_docker_setup(supply_cli=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Docker CLI is missing", result.stderr)
self.assertNotIn("Docker is installed but unavailable", result.stderr)
class ReleaseTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()