erplibre/script/todo/ssh_config.py
Mathieu Benoit 2df3be2df8 [FIX] ssh config : une seule décision sur ce qu'est un nom de machine
Trois lecteurs de ~/.ssh/config tranchaient la même question autrement : l'un
comparait le mot-clé avec la casse, un autre exigeait un espace là où une
tabulation est légale, un troisième laissait passer le motif nié `!nom`. Un
alias déclaré « host » en minuscules était donc vu par l'un et invisible aux
deux autres, ce qui se lit comme une panne intermittente de la découverte.

La décision passe dans un module qui ne lit aucun fichier. Il rend None sur ce
qui n'est pas une déclaration et une liste — possiblement vide — sur ce qui en
est une : « Host * » ne nomme aucune machine mais reste une déclaration, sinon
ses directives se rattachent au bloc précédent. Vérifié : 19 tests, et le menu
reconnaît toujours le serveur du réseau par sa source SSH.

--- EN ---

Three readers of ~/.ssh/config settled the same question differently: one
compared the keyword case-sensitively, another required a space where a tab is
legal, a third let the negated pattern `!name` through. An alias declared with
a lowercase "host" was therefore seen by one and invisible to the other two,
which reads as intermittent discovery failure.

The decision moves into a module that reads no file. It returns None for what
is not a declaration and a list — possibly empty — for what is: "Host *" names
no machine yet remains a declaration, otherwise its directives attach to the
previous block. Checked: 19 tests, and the menu still recognises the server on
the network through its SSH source.

Assisted-by: Claude Opus 5
2026-09-09 07:35:15 -04:00

51 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)
"""Ce qu'est un nom de machine dans ~/.ssh/config, décidé en un seul endroit.
Trois lecteurs de ce fichier vivent dans ce dépôt — l'énumération des alias,
le menu de montage qui veut aussi l'adresse, et l'inventaire des entrées de
VM — et ils divergeaient sur la MÊME question. L'un comparait le mot-clé avec
la casse, un autre exigeait un espace là où une tabulation est légale, un
troisième laissait passer le motif nié. Un alias déclaré `host exo` était donc
vu par l'un et invisible aux autres, ce qui se lit comme une panne
intermittente de la découverte.
Le module ne lit aucun fichier et ne suit pas `Include` : il tranche une
ligne, rien de plus. Un alias déclaré dans un fichier inclus reste donc
invisible aux trois lecteurs, alors même que `ssh -G` le résoudrait — la
source est incomplète sans être fausse.
"""
from __future__ import annotations
import re
# Le mot-clé, suivi d'au moins un blanc. ssh_config ne distingue pas la casse
# de ses mots-clés, et sépare par espace OU tabulation. Le blanc est exigé
# sans quoi `HostName` serait lu comme une déclaration d'hôte.
DECLARATION = re.compile(r"^[ \t]*host[ \t]+", re.IGNORECASE)
def declared_names(line) -> list | None:
"""Les noms de machine d'une ligne « Host », ou None si ce n'en est pas une.
Rend None pour toute autre ligne, et une liste — possiblement VIDE —
quand la ligne est une déclaration. Distinguer les deux compte : `Host *`
EST une déclaration qui ne nomme aucune machine, et un lecteur qui
accumule un bloc doit clore le précédent malgré tout. Rendre `[]` dans
les deux cas rattachait les directives d'un bloc générique au bloc
précédent.
Trois formes ne désignent aucune machine et sortent : les jokers `*` et
`?`, qui décrivent une règle appliquée à plusieurs hôtes, et le motif NIÉ
`!nom`, qui RETIRE un nom de l'ensemble que la ligne vient de décrire.
Retenir un motif nié rend une cible dont le nom commence par `!`, que ssh
ne résoudra jamais.
"""
if not DECLARATION.match(line or ""):
return None
return [
nom
for nom in line.split()[1:]
if "*" not in nom and "?" not in nom and not nom.startswith("!")
]