roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors

LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.

Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :

    ERROR: Could not find a version that satisfies the requirement ansible-core
    Echec temporaire dans la resolution du nom

PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.

--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.

LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
Daniel Allaire 2026-08-28 15:37:09 -04:00
parent ceb832eeac
commit aca63011ea
2 changed files with 116 additions and 2 deletions

View file

@ -153,3 +153,32 @@ serveur_ops_cache_collections: >-
{{ (setops_cache_artefacts | default(lookup('env', 'HOME') + '/.cache/setops')) }}/collections-{{
lookup('file', serveur_ops_requirements_controleur) | hash('sha1') | truncate(12, true, '') }}
serveur_ops_requirements_controleur: "{{ playbook_dir }}/../../requirements.yml"
# --- LA ROUE HORS-LIGNE : PYTHON PAR LE MEME CHEMIN QUE LES COLLECTIONS -------
#
# LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME (2026-08-28). Les collections
# Ansible suivent depuis longtemps l'idiome de ce depot — « le CONTROLEUR telecharge une
# fois, puis pousse par SSH ; aucun flux nouveau depuis la cible, et l'artefact devient
# deployable hors ligne, ce qui est le sens meme d'une lignee autonome ». `pip`, lui,
# sortait encore vers PyPI, en HTTPS et par nom de domaine.
#
# Un tenant n'a NI DNS SORTANT NI 443 vers l'Internet, et c'est voulu. Mesure sur ops-01,
# premiere machine de la reconstruction de Chezlepro :
#
# ERROR: Could not find a version that satisfies the requirement ansible-core<2.19
# Echec temporaire dans la resolution du nom
#
# POURQUOI PAS LE PAQUET DEBIAN : trixie propose `ansible-core 2.19.4`, hors de la plage
# epinglee. Y aller demanderait de deplacer l'epinglage vers une version majeure aux
# changements de gabarits connus, trois jours apres l'avoir pose pour cause de
# portabilite. Ce n'est pas le moment, et ce n'est pas ce probleme-ci.
#
# L'EMPREINTE DES SOURCES ENTRE DANS LE CHEMIN, comme pour les collections : changer une
# ligne de `requirements-python.txt` ou l'epinglage d'ansible-core cree un cache neuf,
# donc un telechargement. Les anciens restent et documentent ce qu'exigeaient les
# versions precedentes.
serveur_ops_roues_source: "{{ playbook_dir }}/../../requirements-python.txt"
serveur_ops_cache_roues: >-
{{ (setops_cache_artefacts | default(lookup('env', 'HOME') + '/.cache/setops')) }}/roues-{{
(lookup('file', serveur_ops_roues_source) ~ serveur_ops_ansible) | hash('sha1') | truncate(12, true, '') }}
serveur_ops_roues_depot: "{{ serveur_ops_racine }}/.roues-hors-ligne"

View file

@ -93,13 +93,97 @@
group: "{{ serveur_ops_utilisateur }}"
mode: "0644"
- name: Installer Ansible dans l'environnement isolé
# --- LA ROUE HORS-LIGNE ------------------------------------------------------
#
# MEME IDIOME QUE LES COLLECTIONS, quelques lignes plus bas : le CONTROLEUR telecharge
# une fois dans son cache, puis pousse par SSH. La cible n'ouvre aucun flux, et
# l'artefact devient deployable hors ligne.
- name: Preparer le cache des roues Python sur le controleur
ansible.builtin.file:
path: "{{ serveur_ops_cache_roues }}"
state: directory
mode: "0755"
delegate_to: localhost
become: false
run_once: true
# LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
#
# `pip download` rend les roues du systeme qui telecharge. `ansible-core`, `proxmoxer` et
# `requests` sont universelles (`py3-none-any`), mais `pyyaml` porte du C compile : une
# roue `cp313` posee sur un python 3.12 ne s'installe pas. L'echec serait « No matching
# distribution found » a l'installation — un message qui accuse le depot local alors que
# le coupable est l'ecart entre deux machines. On le mesure ici, ou on peut encore le dire.
- name: Lire le Python du controleur
ansible.builtin.command:
argv: ["python3", "-c", "import sys; print('%d.%d' % sys.version_info[:2])"]
delegate_to: localhost
become: false
run_once: true
changed_when: false
register: serveur_ops_python_controleur
- name: Exiger que le controleur et la cible partagent leur version de Python
ansible.builtin.assert:
that:
- serveur_ops_python_controleur.stdout | trim
== ansible_python_version.split('.')[0:2] | join('.')
fail_msg: >-
Le controleur tourne Python {{ serveur_ops_python_controleur.stdout | trim }} et
{{ inventory_hostname }} tourne Python
{{ ansible_python_version.split('.')[0:2] | join('.') }}. Les roues telechargees ne
s'installeraient pas sur la cible, et l'echec accuserait le depot local au lieu de
cet ecart. Materialiser depuis un controleur de meme famille — pendant une
insemination, c'est le runner du SITE, qui porte le meme Debian que la machine
qu'il amorce.
# `argv` ET NON `cmd` : le chemin du controleur peut contenir une ESPACE (« Espace
# Chezlepro/… » chez le mainteneur). Meme piege que pour ansible-galaxy le 2026-08-23.
- name: Telecharger les roues sur le controleur (une seule fois par empreinte)
ansible.builtin.command:
argv:
- "python3"
- "-m"
- "pip"
- "download"
- "--dest"
- "{{ serveur_ops_cache_roues }}"
- "--requirement"
- "{{ serveur_ops_roues_source }}"
- "{{ serveur_ops_ansible }}"
- "pyyaml"
delegate_to: localhost
become: false
run_once: true
register: serveur_ops_roues_telechargees
changed_when: "'Saved' in serveur_ops_roues_telechargees.stdout"
# LE GARDE-FOU SUIT LE RESULTAT, PAS LE GESTE (lecon du 2026-08-27) : on saute quand le
# cache PORTE deja des roues, pas quand une commande a deja tourne un jour.
when: not ansible_check_mode
- name: Deposer les roues sur le runner
ansible.builtin.copy:
src: "{{ serveur_ops_cache_roues }}/"
dest: "{{ serveur_ops_roues_depot }}/"
owner: "{{ serveur_ops_utilisateur }}"
group: "{{ serveur_ops_utilisateur }}"
mode: "0644"
directory_mode: "0755"
when: not ansible_check_mode
# `--no-index` EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
# sortir vers PyPI le jour ou le depot local serait incomplet — et la reussite dependrait
# alors d'un flux que ce tenant n'a pas le droit d'avoir. Un repli silencieux vers
# l'Internet transformerait une lignee autonome en lignee qui a l'air autonome.
- name: Installer Ansible dans l'environnement isolé (hors ligne)
ansible.builtin.pip:
name:
- "{{ serveur_ops_ansible }}"
- "pyyaml"
virtualenv: "{{ serveur_ops_venv }}"
state: present
extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}"
become: true
become_user: "{{ serveur_ops_utilisateur }}"
when: not ansible_check_mode
@ -111,11 +195,12 @@
# TACHE SEPAREE : dans `ansible.builtin.pip`, `name` et `requirements` sont MUTUELLEMENT
# EXCLUSIFS. Les fusionner ne produit pas une erreur parlante — le module ignore l'un des
# deux, et la bibliotheque manquante ne se revele qu'au premier module qui en depend.
- name: Installer les bibliotheques Python du controleur (source declaree)
- name: Installer les bibliotheques Python du controleur (source declaree, hors ligne)
ansible.builtin.pip:
requirements: "{{ serveur_ops_racine }}/requirements-python.txt"
virtualenv: "{{ serveur_ops_venv }}"
state: present
extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}"
become: true
become_user: "{{ serveur_ops_utilisateur }}"
when: not ansible_check_mode