[FIX] proxmox : viser la bonne machine, et dire la vérité sur le disque
« s » ouvrait encore la VM locale homonyme : la vue de progression n'avait que le NOM de la VM, et l'entrée ~/.ssh/config n'existe pas encore à ce moment. Le déploiement lui passe maintenant « ssh -J <hôte> user@<ip> », qui ne dépend de rien. Deux voisines du même défaut, jamais rapportées mais aussi graves : la console ouvrait « virsh console <nom> » — celle de la VM LOCALE — et la pause suspendait la locale. Les deux passent par le VMID sur l'hôte. La colonne Disque annonçait « 6.0G/6.0G » sur une VM qui n'avait écrit que 1,2 Go : « du -sb » rend la taille APPARENTE, et un disque raw creux la donne entière. « du -sB1 » compte les blocs. Enfin l'écran de déploiement dit ce qui l'attend : « Quitter (q) pour lancer l'installation d'ERPLibre » — on attendait devant une fenêtre terminée sans le savoir. --- EN --- "s" still opened the homonymous local VM: the progress view only had the VM's NAME, and the ~/.ssh/config entry does not exist yet at that point. The deployment now hands it "ssh -J <host> user@<ip>", which depends on nothing. Two neighbours of the same defect, never reported but just as serious: the console opened "virsh console <name>" — the LOCAL VM's — and pause suspended the local one. Both now go through the VMID on the host. The Disk column claimed "6.0G/6.0G" on a VM that had written 1.2 GB: "du -sb" returns the APPARENT size, and a sparse raw disk gives it in full. "du -sB1" counts blocks. Finally the deployment screen says what awaits it: "Quit (q) to start the ERPLibre install" — one waited before a finished window without knowing. Assisted-by: Claude Opus 5
This commit is contained in:
parent
07a9f66626
commit
4c2adb7c56
6 changed files with 306 additions and 26 deletions
|
|
@ -591,7 +591,9 @@ def res_row_widgets(index, vm, presets, labels=None, null=None):
|
|||
return widgets
|
||||
|
||||
|
||||
def run_deploy_progress(jobs, parallelism, run_app: bool = True):
|
||||
def run_deploy_progress(
|
||||
jobs, parallelism, run_app: bool = True, ssh_cmds=None, suite=""
|
||||
):
|
||||
"""Déploie `jobs` = [(id, nom, argv)] en parallèle, un bloc repliable par
|
||||
VM. Renvoie [(nom, rc, sortie, durée)]. `run_app=False` renvoie l'app.
|
||||
|
||||
|
|
@ -680,9 +682,14 @@ def run_deploy_progress(jobs, parallelism, run_app: bool = True):
|
|||
self.run_job(jid, name, parts)
|
||||
|
||||
def _refresh_summary(self):
|
||||
# CE QUI SUIT, dit ici : l'installation d'ERPLibre ne démarre
|
||||
# qu'au moment où l'on quitte cet écran, et rien ne le disait —
|
||||
# on attendait devant une fenêtre pourtant terminée.
|
||||
fini = self._done >= len(jobs)
|
||||
apres = f"\n → {suite}" if (fini and suite) else ""
|
||||
self.query_one("#summary", Static).update(
|
||||
f" {self._done}/{len(jobs)} — "
|
||||
f"{fmt_dur(time.time() - self._t0)}"
|
||||
f"{fmt_dur(time.time() - self._t0)}{apres}"
|
||||
)
|
||||
|
||||
# `thread=True` : subprocess.run est bloquant ; le faire dans un
|
||||
|
|
@ -765,10 +772,11 @@ def run_deploy_progress(jobs, parallelism, run_app: bool = True):
|
|||
def action_ssh(self) -> None:
|
||||
"""Entre dans la VM créée, sans quitter l'écran.
|
||||
|
||||
Par son NOM et non par son adresse : c'est l'entrée
|
||||
~/.ssh/config que le déploiement vient d'écrire qui sait comment
|
||||
l'atteindre — et pour une VM posée sur un hôte Proxmox, elle
|
||||
porte le rebond, seul chemin vers son réseau interne.
|
||||
La commande vient de l'APPELANT quand il sait joindre la VM : sur
|
||||
Proxmox, l'entrée ~/.ssh/config n'est écrite qu'après le
|
||||
déploiement, et le nom de la VM peut désigner une machine LOCALE
|
||||
homonyme — « s » ouvrait alors la mauvaise. Le déploiement passe
|
||||
donc « ssh -J <hôte> user@<ip> », qui ne dépend de rien.
|
||||
|
||||
« suspend() » rend le terminal : ssh a besoin du clavier, et
|
||||
Textual le tient encore.
|
||||
|
|
@ -780,8 +788,10 @@ def run_deploy_progress(jobs, parallelism, run_app: bool = True):
|
|||
return
|
||||
# La dernière créée : c'est celle qu'on regarde.
|
||||
nom = self._reussies[-1]
|
||||
cmd = (ssh_cmds or {}).get(nom) or f"ssh {shlex_quote(nom)}"
|
||||
with self.suspend():
|
||||
os.system(f"ssh {shlex_quote(nom)} || true")
|
||||
print(f"\n→ {cmd}\n")
|
||||
os.system(f"{cmd} || true")
|
||||
|
||||
# -- presse-papiers (OSC 52 : traverse SSH) --------------------- #
|
||||
def _copy(self, text, what):
|
||||
|
|
|
|||
|
|
@ -967,7 +967,33 @@ class ProxmoxMenuMixin:
|
|||
# sortie ; celle-ci les écrit, ce qui vaut mieux qu'un défilement.
|
||||
session = self._pve_log_dir()
|
||||
print(f"\n {t('Log:')} {session}")
|
||||
resultats = run_deploy_progress(travaux, spec.get("parallelism") or 1)
|
||||
# Comment joindre chaque VM SANS dépendre de ~/.ssh/config, qui n'est
|
||||
# écrit qu'après : par le rebond de l'hôte, explicitement. C'est ce que
|
||||
# « s » utilise dans la vue de progression — sans quoi il partait sur
|
||||
# le nom de la VM, donc sur une locale homonyme (rapporté).
|
||||
cibles_ssh = {}
|
||||
for vm in spec["vms"]:
|
||||
ip = pve.ip_from_ipconfig(vm.get("ipconfig") or "")
|
||||
if ip:
|
||||
compte = (spec.get("user") or "erplibre") + "@" + ip
|
||||
cibles_ssh[vm["name"]] = (
|
||||
f"ssh -J {shlex.quote(host['target'])} "
|
||||
f"{shlex.quote(compte)}"
|
||||
)
|
||||
# Ce qui attend derrière cet écran : sans le dire, on reste devant
|
||||
# une fenêtre « terminée » sans savoir que l'installation d'ERPLibre
|
||||
# démarre en la quittant.
|
||||
suite = ""
|
||||
if spec.get("install"):
|
||||
suite = t("Quit (q) to start the ERPLibre install")
|
||||
elif spec.get("monitor", True):
|
||||
suite = t("Quit (q) to follow the VM starting up")
|
||||
resultats = run_deploy_progress(
|
||||
travaux,
|
||||
spec.get("parallelism") or 1,
|
||||
ssh_cmds=cibles_ssh,
|
||||
suite=suite,
|
||||
)
|
||||
reussies = [nom for nom, code, _o, _d in resultats if code == 0]
|
||||
for nom, code, sortie, duree in resultats:
|
||||
chemin = self._pve_write_log(
|
||||
|
|
@ -1215,6 +1241,10 @@ class ProxmoxMenuMixin:
|
|||
"sudo": host.get("sudo") or "",
|
||||
"jump": host.get("jump") or "",
|
||||
"vmid": vm.get("vmid"),
|
||||
# L'adresse INTERNE : elle n'est pas routable d'ici, mais
|
||||
# elle l'est depuis l'hôte. Avec le rebond, le tableau de
|
||||
# bord entre dans la VM sans dépendre de ~/.ssh/config.
|
||||
"addr": vm.get("adresse") or "",
|
||||
}
|
||||
for vm in joignables
|
||||
if vm.get("vmid")
|
||||
|
|
|
|||
|
|
@ -1309,10 +1309,14 @@ def read_domstats() -> str:
|
|||
# disque et le cumul écrit de TOUTES ses VM d'un coup. Le « du » qui suit
|
||||
# donne la taille RÉELLEMENT occupée : sur un stockage en fichiers, Proxmox
|
||||
# rapporte « disk: 0 » — il ne la calcule pas.
|
||||
#
|
||||
# « -sB1 » et NON « -sb » : le second rend la taille APPARENTE, et un disque
|
||||
# raw creux de 6 Go la donne entière. La colonne affichait donc « 6.0G/6.0G »,
|
||||
# un disque plein, quand l'invité n'avait écrit que 1,2 Go — rapporté.
|
||||
PVE_STATS_CMD = (
|
||||
"pvesh get /cluster/resources --type vm --output-format json;"
|
||||
" echo '---ERPLIBRE-DU---';"
|
||||
" du -sb /var/lib/vz/images/*/ 2>/dev/null || true"
|
||||
" du -sB1 /var/lib/vz/images/*/ 2>/dev/null || true"
|
||||
)
|
||||
# Une VM distante se relève moins souvent qu'une locale : chaque tour coûte
|
||||
# une poignée de main ssh (mesuré 1 s), quand « virsh domstats » coûte 0,03 s
|
||||
|
|
@ -1337,7 +1341,7 @@ def parse_pvestats(text: str) -> dict:
|
|||
ressources = json.loads(brut.strip() or "[]")
|
||||
except ValueError:
|
||||
return {}
|
||||
# {vmid: octets} depuis « du -sb /var/lib/vz/images/<vmid>/ ».
|
||||
# {vmid: octets} depuis « du -sB1 /var/lib/vz/images/<vmid>/ ».
|
||||
occupe = {}
|
||||
for ligne in tailles.splitlines():
|
||||
parts = ligne.split()
|
||||
|
|
@ -1404,6 +1408,46 @@ def read_pvestats(vms, now=None) -> dict:
|
|||
return dict(stats)
|
||||
|
||||
|
||||
def vm_ssh_prefix(vm) -> str:
|
||||
"""« ssh … » pour entrer dans CETTE VM, adresse comprise.
|
||||
|
||||
Une VM d'un hôte Proxmox vit derrière lui : son adresse n'est pas
|
||||
routable d'ici, et seul le rebond y mène. On le construit explicitement
|
||||
plutôt que de compter sur un alias ~/.ssh/config, qui peut ne pas exister
|
||||
— ou, pire, désigner une VM LOCALE homonyme. C'est ce qui a fait ouvrir
|
||||
la mauvaise machine avec « s ».
|
||||
"""
|
||||
info = (vm or {}).get("pve") or {}
|
||||
adresse = info.get("addr")
|
||||
if info.get("target") and adresse:
|
||||
saut = f"-J {shlex.quote(info['jump'])} " if info.get("jump") else ""
|
||||
return (
|
||||
f"ssh {SSH_OPTS} {saut}-J {shlex.quote(info['target'])} "
|
||||
f"erplibre@{adresse}"
|
||||
)
|
||||
return f"ssh {SSH_OPTS} erplibre@{(vm or {}).get('ip')}"
|
||||
|
||||
|
||||
def pve_host_cmd(info, remote, tty=False) -> str:
|
||||
"""Commande shell qui exécute `remote` SUR l'hôte Proxmox d'une VM.
|
||||
|
||||
Chaque action du tableau de bord qui parlait à libvirt par le NOM frappait
|
||||
la mauvaise machine dès qu'un domaine local portait le même : la console
|
||||
ouvrait celle de la VM locale, la pause suspendait la locale. L'hôte est
|
||||
la seule autorité pour une VM distante, et le VMID son seul identifiant.
|
||||
"""
|
||||
sudo = (info or {}).get("sudo") or ""
|
||||
cible = (info or {}).get("target") or ""
|
||||
prefixe = f"{sudo}sh -c {shlex.quote(remote)}" if sudo else remote
|
||||
saut = (
|
||||
f"-J {shlex.quote(info['jump'])} " if (info or {}).get("jump") else ""
|
||||
)
|
||||
return (
|
||||
f"ssh {'-t ' if tty else ''}{saut}{shlex.quote(cible)} "
|
||||
f"{shlex.quote(prefixe)}"
|
||||
)
|
||||
|
||||
|
||||
def arm_balloon(names) -> None:
|
||||
"""Arme la période de collecte du ballon (5 s) sur chaque VM.
|
||||
|
||||
|
|
@ -2384,8 +2428,10 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
vm = self._vm_by_name(self._selected)
|
||||
if not vm:
|
||||
return
|
||||
cmd = vm_ssh_prefix(vm)
|
||||
with self.suspend():
|
||||
os.system(f"ssh {SSH_OPTS} erplibre@{vm['ip']} || true")
|
||||
print(f"\n→ {cmd}\n")
|
||||
os.system(f"{cmd} || true")
|
||||
|
||||
def action_console(self) -> None:
|
||||
"""Console série de la VM, sans quitter le suivi.
|
||||
|
|
@ -2402,18 +2448,33 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
vm = self._vm_by_name(self._selected)
|
||||
if not vm:
|
||||
return
|
||||
name = shlex.quote(vm["name"])
|
||||
info = vm.get("pve")
|
||||
if info:
|
||||
# VM d'un hôte Proxmox : sa console est « qm terminal », sur
|
||||
# l'hôte. « virsh console <nom> » ouvrait celle du domaine
|
||||
# LOCAL homonyme — la mauvaise machine, sans le dire.
|
||||
cmd = pve_host_cmd(
|
||||
info, f"qm terminal {int(info.get('vmid') or 0)}", tty=True
|
||||
)
|
||||
titre = (
|
||||
f"qm terminal {info.get('vmid')} @ {info.get('target')}"
|
||||
)
|
||||
sortie = "Ctrl+O"
|
||||
else:
|
||||
cmd = f"sudo virsh console {shlex.quote(vm['name'])}"
|
||||
titre = f"virsh console {vm['name']}"
|
||||
sortie = "Ctrl+]"
|
||||
with self.suspend():
|
||||
# La console n'affiche que ce qui arrive APRÈS l'attachement :
|
||||
# sur une VM déjà démarrée l'écran reste noir tant qu'on n'a
|
||||
# rien envoyé. On le dit, plutôt que de laisser croire à un gel.
|
||||
print(f"\n→ virsh console {vm['name']}")
|
||||
print(f"\n→ {titre}")
|
||||
print(
|
||||
" Écran vide ? Appuyez sur Entrée : la console ne montre"
|
||||
" que la sortie qui suit l'attachement."
|
||||
)
|
||||
print(" Ctrl+] puis Entrée pour revenir au suivi.\n")
|
||||
os.system(f"sudo virsh console {name} || true")
|
||||
print(f" {sortie} puis Entrée pour revenir au suivi.\n")
|
||||
os.system(f"{cmd} || true")
|
||||
|
||||
def action_web(self) -> None:
|
||||
"""Ouvre l'UI web de la VM (Odoo :8069) dans un navigateur CLI
|
||||
|
|
@ -2552,15 +2613,30 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
|
||||
# -- pause / reprise de tout le parc -------------------------------- #
|
||||
@staticmethod
|
||||
def _virsh_bulk(action, names):
|
||||
for n in names:
|
||||
def _virsh_bulk(action, cibles):
|
||||
"""Suspend/reprend chaque VM, chacune par SON hyperviseur.
|
||||
|
||||
`cibles` : [(nom, info_pve|None)]. Une VM distante se suspend par
|
||||
son VMID sur son hôte — « virsh suspend <nom> » aurait mis en
|
||||
pause le domaine LOCAL homonyme."""
|
||||
for nom, info in cibles:
|
||||
try:
|
||||
subprocess.run(
|
||||
["sudo", "virsh", action, n],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
if info:
|
||||
vmid = int(info.get("vmid") or 0)
|
||||
subprocess.run(
|
||||
pve_host_cmd(info, f"qm {action} {vmid}"),
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
else:
|
||||
subprocess.run(
|
||||
["sudo", "virsh", action, nom],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
|
||||
|
|
@ -2701,8 +2777,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
with self.suspend():
|
||||
print(f"\n=== {title} — {vm['name']} ===")
|
||||
os.system(
|
||||
f"ssh {SSH_OPTS} erplibre@{vm['ip']} "
|
||||
f"{shlex.quote(cmd)} || true"
|
||||
f"{vm_ssh_prefix(vm)} " f"{shlex.quote(cmd)} || true"
|
||||
)
|
||||
input("\nEntrée pour revenir au suivi… ")
|
||||
|
||||
|
|
@ -2751,7 +2826,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
async def _bulk_worker(self, action):
|
||||
want = "running" if action == "suspend" else "paused"
|
||||
targets = [
|
||||
vm["name"]
|
||||
(vm["name"], vm.get("pve"))
|
||||
for vm in vms
|
||||
if self._domstate.get(vm["name"]) == want
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3986,6 +3986,14 @@ TRANSLATIONS = {
|
|||
"fr": "Fichiers orphelins :",
|
||||
"en": "Orphan files:",
|
||||
},
|
||||
"Quit (q) to start the ERPLibre install": {
|
||||
"fr": "Quitter (q) pour lancer l'installation d'ERPLibre",
|
||||
"en": "Quit (q) to start the ERPLibre install",
|
||||
},
|
||||
"Quit (q) to follow the VM starting up": {
|
||||
"fr": "Quitter (q) pour suivre la mise en route de la VM",
|
||||
"en": "Quit (q) to follow the VM starting up",
|
||||
},
|
||||
"A local VM already bears this name:": {
|
||||
"fr": "Une VM locale porte déjà ce nom :",
|
||||
"en": "A local VM already bears this name:",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,88 @@ class TestLaBonneMachine(unittest.TestCase):
|
|||
self.assertIn("ip=cible", script)
|
||||
|
||||
|
||||
class TestLeDisque(unittest.TestCase):
|
||||
"""La colonne Disque annonçait un disque PLEIN qui ne l'était pas.
|
||||
|
||||
Rapporté : « 6.0G/6.0G » sur une VM dont l'invité disait « 845M utilisés
|
||||
sur 5.8G ». La mesure venait de « du -sb », qui rend la taille APPARENTE :
|
||||
un disque raw creux la donne entière. « du -sB1 » compte les blocs
|
||||
réellement occupés — 1,2 Go, ce qui correspond.
|
||||
"""
|
||||
|
||||
def test_the_command_counts_real_blocks(self):
|
||||
self.assertIn("du -sB1", mon.PVE_STATS_CMD)
|
||||
self.assertNotIn("du -sb", mon.PVE_STATS_CMD)
|
||||
|
||||
def test_the_measure_is_read_per_vmid(self):
|
||||
texte = (
|
||||
'[{"vmid":101,"name":"vm-a","disk":0,"maxdisk":6442450944,'
|
||||
'"mem":1,"maxmem":2,"diskwrite":0,"status":"running","uptime":1}]\n'
|
||||
"---ERPLIBRE-DU---\n"
|
||||
"1268518912\t/var/lib/vz/images/101/\n"
|
||||
"4294967296\t/var/lib/vz/images/999/\n"
|
||||
)
|
||||
rec = mon.parse_pvestats(texte)["vm-a"]
|
||||
self.assertEqual(rec["disk_used"], 1268518912)
|
||||
self.assertEqual(rec["disk_total"], 6442450944)
|
||||
self.assertEqual(
|
||||
mon.fmt_pair(rec["disk_used"], rec["disk_total"]), "1.2G/6.0G"
|
||||
)
|
||||
|
||||
|
||||
class TestOuVaLaCommande(unittest.TestCase):
|
||||
"""Chaque action doit viser la BONNE machine.
|
||||
|
||||
Une VM distante et un domaine local peuvent porter le même nom : « s »
|
||||
ouvrait la locale, la console ouvrait la console de la locale, et la pause
|
||||
suspendait la locale. Le VMID et le rebond sont les seules désignations
|
||||
qui ne trompent pas.
|
||||
"""
|
||||
|
||||
LOCALE = {"name": "vm-a", "ip": "192.168.123.118"}
|
||||
DISTANTE = {
|
||||
"name": "vm-a",
|
||||
"ip": "pve1+vm-a",
|
||||
"pve": {
|
||||
"target": "erplibre@pve1",
|
||||
"sudo": "sudo ",
|
||||
"jump": "",
|
||||
"vmid": 101,
|
||||
"addr": "10.10.10.151",
|
||||
},
|
||||
}
|
||||
|
||||
def test_ssh_to_a_local_vm_uses_its_address(self):
|
||||
self.assertIn(
|
||||
"erplibre@192.168.123.118", mon.vm_ssh_prefix(self.LOCALE)
|
||||
)
|
||||
self.assertNotIn("-J", mon.vm_ssh_prefix(self.LOCALE))
|
||||
|
||||
def test_ssh_to_a_remote_vm_goes_through_the_jump(self):
|
||||
cmd = mon.vm_ssh_prefix(self.DISTANTE)
|
||||
self.assertIn("-J", cmd)
|
||||
self.assertIn("erplibre@pve1", cmd)
|
||||
self.assertIn("erplibre@10.10.10.151", cmd)
|
||||
|
||||
def test_without_an_address_it_falls_back_to_the_alias(self):
|
||||
vm = {"name": "vm-a", "ip": "pve1+vm-a", "pve": {"target": "pve1"}}
|
||||
self.assertIn("erplibre@pve1+vm-a", mon.vm_ssh_prefix(vm))
|
||||
|
||||
def test_the_console_of_a_remote_vm_is_qm_terminal(self):
|
||||
cmd = mon.pve_host_cmd(
|
||||
self.DISTANTE["pve"], "qm terminal 101", tty=True
|
||||
)
|
||||
self.assertIn("ssh -t", cmd)
|
||||
self.assertIn("qm terminal 101", cmd)
|
||||
self.assertIn("sudo", cmd)
|
||||
# Et surtout PAS virsh, qui viserait le domaine local homonyme.
|
||||
self.assertNotIn("virsh", cmd)
|
||||
|
||||
def test_a_host_without_sudo_is_not_wrapped(self):
|
||||
cmd = mon.pve_host_cmd({"target": "root@pve1", "sudo": ""}, "qm list")
|
||||
self.assertNotIn("sh -c", cmd)
|
||||
|
||||
|
||||
class TestLEtat(unittest.TestCase):
|
||||
"""Une VM absente de « virsh list » passait pour EFFACÉE."""
|
||||
|
||||
|
|
|
|||
|
|
@ -142,5 +142,80 @@ class TestLaToucheSsh(unittest.TestCase):
|
|||
self.assertEqual(vu["reussies"], [])
|
||||
|
||||
|
||||
@unittest.skipUnless(TEXTUAL, "Textual absent")
|
||||
class TestCeQuiSuit(unittest.TestCase):
|
||||
"""Rapporté : on attendait devant une fenêtre « terminée » sans savoir
|
||||
que l'installation d'ERPLibre démarre en la quittant."""
|
||||
|
||||
def _sommaire(self, suite, attendre=3.0):
|
||||
# Un travail qui DURE : sinon il finit avant le premier relevé, et le
|
||||
# test ne prouve rien de l'avant/après.
|
||||
jobs = [("1", "vm-a", ["bash", "-c", "sleep 2; echo ok"])]
|
||||
vu = {}
|
||||
|
||||
async def scenario():
|
||||
from textual.widgets import Static
|
||||
|
||||
app = run_deploy_progress(jobs, 1, run_app=False, suite=suite)
|
||||
async with app.run_test(size=(140, 30)) as pilote:
|
||||
await pilote.pause()
|
||||
vu["pendant"] = str(app.query_one("#summary", Static).render())
|
||||
await asyncio.sleep(attendre)
|
||||
await pilote.pause()
|
||||
vu["apres"] = str(app.query_one("#summary", Static).render())
|
||||
|
||||
asyncio.run(scenario())
|
||||
return vu
|
||||
|
||||
def test_it_says_what_follows_once_everything_is_done(self):
|
||||
vu = self._sommaire("Quitter (q) pour lancer l'installation")
|
||||
self.assertNotIn("Quitter", vu["pendant"])
|
||||
self.assertIn("Quitter", vu["apres"])
|
||||
|
||||
def test_nothing_is_promised_when_nothing_follows(self):
|
||||
vu = self._sommaire("")
|
||||
self.assertNotIn("→", vu["apres"])
|
||||
|
||||
|
||||
@unittest.skipUnless(TEXTUAL, "Textual absent")
|
||||
class TestLaCibleSsh(unittest.TestCase):
|
||||
"""« s » utilisait le NOM de la VM. Sur Proxmox, l'entrée ~/.ssh/config
|
||||
n'existe pas encore à ce moment — et ce nom peut désigner une machine
|
||||
LOCALE homonyme, qui s'ouvrait alors à sa place."""
|
||||
|
||||
def _lance(self, ssh_cmds):
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
jobs = [("1", "erplibre-ubuntu-2604", ["bash", "-c", "echo ok"])]
|
||||
vu = []
|
||||
|
||||
async def scenario():
|
||||
app = run_deploy_progress(
|
||||
jobs, 1, run_app=False, ssh_cmds=ssh_cmds
|
||||
)
|
||||
async with app.run_test(size=(140, 30)) as pilote:
|
||||
await pilote.pause()
|
||||
await asyncio.sleep(1.2)
|
||||
await pilote.pause()
|
||||
vrai = os.system
|
||||
os.system = vu.append
|
||||
app.suspend = lambda: contextlib.nullcontext()
|
||||
try:
|
||||
app.action_ssh()
|
||||
finally:
|
||||
os.system = vrai
|
||||
|
||||
asyncio.run(scenario())
|
||||
return vu[0] if vu else ""
|
||||
|
||||
def test_the_given_command_wins(self):
|
||||
cible = "ssh -J erplibre-proxmox-9 erplibre@10.10.10.151"
|
||||
self.assertIn(cible, self._lance({"erplibre-ubuntu-2604": cible}))
|
||||
|
||||
def test_without_one_it_falls_back_to_the_name(self):
|
||||
self.assertIn("ssh erplibre-ubuntu-2604", self._lance(None))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
|
|
|||
Loading…
Reference in a new issue