VMs of one host pulled the same packages again and again, and an install could not be proven to work without the network. A Go service intercepts the bridge transparently: a package file is served from disk, an index is always revalidated upstream and replayed only when upstream is mute, git is mirrored. Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut the network for a whole install. The TODO menu installs, diagnoses, fills, cleans and copies the store; long_test/qemu_cache.py measures that a second VM pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py. --- FR --- Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait qu'une installation marche sans réseau. Un service Go intercepte le pont de façon transparente : un fichier de paquet est servi du disque, un index est toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git est mis en miroir. Le déploiement pose l'autorité du cache dans les invités QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ; long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py. Assisted-by: Claude Opus 5
67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""Le miroir apt d'une VM Ubuntu, tel que son cloud-config l'écrit.
|
|
|
|
Derrière le cache, le miroir est fixe (« uri: ») : la recherche de
|
|
cloud-init (« search: ») écarte tout miroir derrière un résolveur qui répond
|
|
à tout nom, et une VM qui tire d'un autre miroir que les précédentes ne
|
|
retrouve rien de ce que le cache a gardé.
|
|
"""
|
|
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
RACINE = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(RACINE))
|
|
|
|
from script.qemu import deploy_qemu as DQ # noqa: E402
|
|
|
|
|
|
class TestLeMiroirApt(unittest.TestCase):
|
|
def _apt(self, *extra):
|
|
args = DQ.build_parser().parse_args(
|
|
["--distro", "ubuntu", "--hostname", "vm", *extra]
|
|
)
|
|
return yaml.safe_load(DQ.build_cloud_config(args, None, []))["apt"]
|
|
|
|
def _ca(self):
|
|
f = tempfile.NamedTemporaryFile(
|
|
"w", suffix=".pem", delete=False, encoding="utf-8"
|
|
)
|
|
self.addCleanup(Path(f.name).unlink)
|
|
f.write(
|
|
"-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n"
|
|
)
|
|
f.close()
|
|
return f.name
|
|
|
|
def test_sans_cache_cloud_init_cherche(self):
|
|
apt = self._apt()
|
|
for bloc in ("primary", "security"):
|
|
self.assertEqual(apt[bloc][0]["search"], DQ.APT_MIRRORS_MAIN)
|
|
self.assertNotIn("uri", apt[bloc][0])
|
|
|
|
def test_derriere_le_cache_le_premier_miroir_est_fixe(self):
|
|
apt = self._apt("--cache-ca", self._ca())
|
|
for bloc in ("primary", "security"):
|
|
self.assertEqual(apt[bloc][0]["uri"], DQ.APT_MIRRORS_MAIN[0])
|
|
self.assertNotIn("search", apt[bloc][0])
|
|
|
|
def test_un_miroir_impose_est_fixe(self):
|
|
miroir = "http://miroir.invalid/ubuntu"
|
|
apt = self._apt("--apt-mirror", miroir)
|
|
self.assertEqual(apt["primary"][0]["uri"], miroir)
|
|
self.assertNotIn("search", apt["primary"][0])
|
|
|
|
def test_les_arches_ports_fixent_leur_propre_miroir(self):
|
|
lignes = DQ.apt_mirror_lines("arm64", fixe=True)
|
|
self.assertIn(f" uri: {DQ.APT_MIRRORS_PORTS[0]}", lignes)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|