Supporter plusieurs versions de systèmes#

Observation#

Il peut arriver de vouloir supporter :

  • différents systèmes

  • différentes versions d’un même système

Ansible collecte des facts à la connexion au système pour nous permettre de différencier un OS d’un autre. C’est classiquement sur ces variables collectées qu’il convient de s’appuyer pour savoir dans quel cas l’exécution se déroule.

Pour cela, vous trouverez couramment des approches basées sur l’exécution conditionnelle de tasks.

#
# Ne pas reproduire chez vous
# 
- name: Debian-only templating
  template:
    src: ...
    dest: ...
  when: ansible_distribution == 'Debian'

Le problème latent est la duplication de code, qui arrive rapidement avec la multiplication des cas particuliers.

Le niveau supérieur est de charger un fichier de tasks dédié en s’appuyant sur la même variable :

#
# Ne pas reproduire chez vous
# 
- name: Debian-only tasks
  include_tasks: debian.yml
  when: ansible_distribution == 'Debian'

…ou encore :

#
# Ne pas reproduire chez vous
# 
- name: Debian-only tasks
  include_tasks: "{{ ansible_distribution | lower }}.yml

Mais là encore on va rapidement se retrouver avec des cas et des sous-cas au fil de la vie du code.

Proposition#

L’organisation de code pour pouvoir supporter des tasks spécifiques par système est un problème récurrent et chaque projet y va de son implémentation. Voici une méthode tout-terrain.

L’idée est de créer un fois pour toutes une chaine de chargement préférentiel qui s’appuie sur nos facts. L’inclusion de facts dans le nommage des fichiers recherchés permet de ne charger que les cas les plus spécifiques, et cela pour des fichiers de variable ET pour des fichiers de tasks.

- name: OS markers
  debug:
    msg: >-
      Distrib: {{ ansible_distribution | lower }} - Version: {{ ansible_distribution_major_version }} - Arch: {{ ansible_architecture | lower }}
    verbosity: 1

- name: Load os-specific vars
  include_vars: "{{ _current_os_vars }}"
  with_first_found:
    - skip: true
      files:
        - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}_{{ ansible_architecture | lower }}.yml"
        - "{{ ansible_distribution | lower }}_{{ ansible_architecture | lower }}.yml"
        - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}.yml"
        - "{{ ansible_distribution | lower }}.yml"
        - "{{ ansible_os_family | lower }}.yml"
  loop_control:
    loop_var: _current_os_vars

- name: Execute os-specific tasks
  include_tasks: "{{ _current_os_tasks }}"
  with_first_found:
    - skip: true
      files:
        - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}_{{ ansible_architecture | lower }}/main.yml"
        - "{{ ansible_distribution | lower }}_{{ ansible_architecture | lower }}/main.yml"
        - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}/main.yml"
        - "{{ ansible_distribution | lower }}/main.yml"
  loop_control:
    loop_var: _current_os_tasks

Concrètement, ce code exécuté depuis un rôle sur une machine Debian Bullseye d’architecture x86 va :

  • tenter de charger le fichier de variables vars/debian_11_x86_64.yml ;

  • si le fichier est absent, tenter vars/debian_x86_64.yml ;

  • si le fichier est absent, tenter vars/debian_11.yml ;

  • si le fichier est absent, tenter vars/debian.yml ;

  • si le fichier est absent, passer à la suite sans erreur ;

  • tenter d’exécuter le fichier de tasks tasks/debian_11_x86_64/main.yml ;

  • si le fichier est absent, tenter tasks/debian_x86_64/main.yml ;

  • si le fichier est absent, tenter tasks/debian_11/main.yml ;

  • si le fichier est absent, tenter tasks/debian/main.yml ;

  • si le fichier est absent, passer à la suite sans erreur.

Intégration#

Une fois ce code en place dans le fichier tasks/main.yml d’un rôle, vous n’aurez plus besoin d’y toucher. Implémenter des variables et comportements spécifiques reviendra à ajouter les fichiers à charger en suivant la convention de nommage des fichiers.

À vous de trouvez la priorisation qui convient le mieux à vos usages et de poser une convention avec vos équipes.