[FIX] proxmox : donner l'URL au remède qui retire l'entrée du cache
Quand la somme d'une image ne correspond pas, le message nomme le geste à faire EN PREMIER, cet hôte pouvant être lui-même derrière le cache. Il rendait « printf %s | … --oublie » : une f-string dont le marqueur était tombé, et un printf sans opérande. Recopiée, la commande lit un flux vide, n'efface rien et sort à 0 — on croit avoir purgé, on efface l'image, on relance, et le magasin ressert les mêmes octets indéfiniment. Un remède qui réussit sans rien faire est pire que pas de remède. La voie libvirt donnait déjà l'URL ; celle-ci la donne aussi. Le test la joue dans un vrai shell, la ligne traversant deux quotages. --- EN --- When an image's checksum does not match, the message names the step to take FIRST, this host possibly being behind the cache itself. It printed « printf %s | … --oublie »: an f-string whose placeholder had been lost, and a printf with no operand. Copied, the command reads an empty stream, erases nothing and exits 0 — you believe you purged, you erase the image, you retry, and the store serves the same bytes for ever. A remedy that succeeds without doing anything is worse than none. The libvirt path already gave the URL; this one now does too. The test plays it in a real shell, the line crossing two levels of quoting. Assisted-by: Claude Opus 5
This commit is contained in:
parent
f34c0d2ded
commit
34ac3e775d
2 changed files with 43 additions and 1 deletions
|
|
@ -993,10 +993,17 @@ def image_fetch_cmd(
|
|||
# depuis le magasin. L'entrée s'en retire d'abord — « --purge »
|
||||
# efface tout, et « --purge-older-than » n'atteint jamais un objet
|
||||
# que chaque service rajeunit.
|
||||
# L'URL est DANS la commande proposée. Sans elle, « printf %s » n'a
|
||||
# pas d'opérande, n'écrit rien, et « --oublie » lit un flux vide puis
|
||||
# sort à 0 : l'opérateur croit avoir purgé, efface l'image, relance,
|
||||
# et le magasin ressert les mêmes octets. Un remède qui réussit sans
|
||||
# rien faire est pire que pas de remède.
|
||||
aide = (
|
||||
f"rm -f {shlex.quote(cible)} et relancer ;"
|
||||
" derrière un cache de téléchargement, en retirer l'entrée"
|
||||
f" d'abord : printf %s | sudo erplibre_go_qemu_cache --oublie"
|
||||
" d'abord : printf '%s\\n' "
|
||||
+ shlex.quote(f"GET {url}")
|
||||
+ " | sudo erplibre_go_qemu_cache --oublie"
|
||||
)
|
||||
cmd += (
|
||||
f" && {{ {somme(cible)} || {{ "
|
||||
|
|
|
|||
|
|
@ -416,6 +416,41 @@ class TestLesCommandes(unittest.TestCase):
|
|||
# Vers l'erreur standard : la sortie normale est lue par la machine.
|
||||
self.assertIn(">&2", cmd)
|
||||
|
||||
def test_the_remedy_names_the_url_it_forgets(self):
|
||||
"""Le message dit de lancer cette commande EN PREMIER, et une
|
||||
commande qu'on ne peut pas recopier telle quelle vaut moins que pas
|
||||
de commande.
|
||||
|
||||
« printf %s » sans opérande n'écrit rien ; « --oublie » lit alors un
|
||||
flux vide, n'efface rien et sort à 0. L'opérateur croit avoir purgé,
|
||||
efface l'image, relance — et le magasin ressert les mêmes octets,
|
||||
indéfiniment. Le succès silencieux est le pire des deux."""
|
||||
url = "https://exemple.invalide/img/deb.qcow2"
|
||||
cmd = pve.image_fetch_cmd(url, "deb.qcow2", sha256="ab" * 32)
|
||||
self.assertIn(url, cmd)
|
||||
oubli = cmd[cmd.index("printf") : cmd.index("--oublie")]
|
||||
self.assertIn(url, oubli)
|
||||
self.assertIn("GET ", oubli)
|
||||
|
||||
def test_the_remedy_survives_a_real_shell(self):
|
||||
"""Joué pour de vrai : c'est la LIGNE RENDUE qui compte, pas la
|
||||
chaîne Python. Elle traverse deux quotages — celui de l'aide, puis
|
||||
celui du « echo » qui la porte — et une interpolation tombée ne se
|
||||
voit qu'ici."""
|
||||
import subprocess
|
||||
|
||||
url = "https://exemple.invalide/img/deb.qcow2"
|
||||
cmd = pve.image_fetch_cmd(url, "deb.qcow2", sha256="ab" * 32)
|
||||
aide = cmd[cmd.index("echo 'rm -f") + len("echo ") :]
|
||||
aide = aide[: aide.index(" >&2")]
|
||||
rendu = subprocess.run(
|
||||
["sh", "-c", f"echo {aide}"], capture_output=True, text=True
|
||||
)
|
||||
self.assertEqual(rendu.returncode, 0, rendu.stderr)
|
||||
self.assertIn(f"'GET {url}'", rendu.stdout)
|
||||
# Le tube est complet : un « printf » nu ne serait pas recopiable.
|
||||
self.assertIn("| sudo erplibre_go_qemu_cache --oublie", rendu.stdout)
|
||||
|
||||
def test_the_failure_is_still_a_failure(self):
|
||||
"""Un message n'absout pas : « false » garde le code de retour, sans
|
||||
quoi le déploiement continuerait sur une image substituée."""
|
||||
|
|
|
|||
Loading…
Reference in a new issue