Table des matières
- 2026:
- 2025:
1 billet(s) pour avril 2026
| Notes ping ICMP | 2026/04/03 23:01 | Jean-Baptiste |
Notes Ansible Jinja2
Voir :
- j2cli
A la place du module template il est possible d'utiliser le module copy : Ansible M(template) M(copy)
- name: Generate new resolv.conf ansible.builtin.copy: mode: "{{ stat_resolv_conf.stat.mode }}" content: | {% for nameserver in nameservers %} nameserver {{ nameserver }} {% endfor %} dest: resolv.conf.test
Ansible jinja version
$ ansible --version |grep jinja jinja version = 3.1.6
Jinja2 en ligne de commande j2cli j2
Install
apt-get install j2cli
ou
pip3 install jinja2-cli[yaml,toml,xml,hjson,json5]==0.8.2 # --break-system-packages
Exemples
Exemple 1
nginx.conf.j2
server { listen 80; server_name {{ nginx.hostname }}; root {{ nginx.webroot }}; index index.htm; }
nginx.yaml
--- nginx: hostname: localhost webroot: "/var/html/projet1"
# j2 -f json nginx.conf.j2 nginx.json > nginx.conf j2 nginx.conf.j2 nginx.yaml > nginx.conf
Exemple 2
timezone.j2
{{ TIMEZONE }}
ENV
TIMEZONE=Europe/Paris
j2 -f env timezone.j2 ENV > timezone
Regex
{{ requete_conteneur.stdout | regex_replace('\\s', '') }}
Exclure un élément d'une liste / enlever un élément d'une liste.
- name: DEBUG debug: msg='{{ item }}' with_items: '{{ ansible_interfaces |difference(["lo"]) }}'
Condition IF
/etc/yum.repos.d/plop.repo
{% if ansible_distribution_version == '7.2' %}
[mirorlinux-rh7.2-x86_64]
name=Plop RH 7.2
baseurl=http://172.16.12.42/redhat/rh7.2/x86_64/
gpgcheck=0
enabled=1
{% endif %}
{% for user in users if not user.hidden %}
<li>{{ user.username|e }}</li>
{% endfor %}
Valeur par défaut - variable undef
Erreur
fatal: [vmware-local]: FAILED! => {"failed": true, "msg": "'servers' is undefined"}
Code Ansible
- name: conf /etc/chrony.conf - add servers lineinfile: dest=/etc/chrony.conf line='server {{ item }} iburst' with_items: - "{{ servers }}" - 172.18.32.3
Code Ansible corrigé
- name: conf /etc/chrony.conf - add servers lineinfile: dest=/etc/chrony.conf line='server {{ item }} iburst' with_items: #- "{{ servers |default('') }}" - "{{ servers |default([]) }}" - 172.18.32.3
Liste vide avec item undef - with_item undef / empty :
{{ item |default([]) }}''
Affichage - Alignement
Avec la méthode ljust ou rjust
{% for HOST in hosts %}
define host {
{% for k, v in HOST.items() %}
{{ k.ljust(20) }} {{ v }}
{% endfor %}
}
{% endfor %}
Lever une exeption
Source https://stackoverflow.com/questions/21778252/how-to-raise-an-exception-in-a-jinja2-macro
Avec Ansibe
{{ ('OK text' if condition_ok) | mandatory('Text of error message') }}
{{ ('' if false) | mandatory('ERROR: KIND must be A, B or C') }}
Ou sinon plus simple :
{{ 0/0 }}
Linter
Voir :
sudo apt-get install python3-pip
python3 -m venv djlint source bin/activate pip install djlint
$ ./bin/djlint ~/code/template-dockerfile/ --extension=j2 --lint Linting 1/1 files ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 00:00 Dockerfile.j2 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── H025 19:9 Tag seems to be an orphan. <EOF > H014 132:27 Found extra blank lines. e/pcc/logs Linted 1 file, found 2 errors.
djlint . --extension=html.j2 --lint djlint . --extension=html.j2 --check djlint . --extension=html.j2 --reformat
Exemples
Variables - Whitespace Control - Exemple de contrôles de sauts de ligne et d'espaces
Méthode 1
- Utilisation de
+dans la boucle - Utilisation de
-dans les conditions à l'interieur de la boucle - A la fin de la dernière condition (
endif) utiliser un+à la place d'un-
{%+ for KEY in CONF.SYS_GROUPS +%}
{%- if CONF.SYS_GROUPS[KEY].GID is defined -%}
RUN groupadd -g {{ CONF.SYS_GROUPS[KEY].GID }} {{ KEY }}
{%- else -%}
RUN groupadd {{ KEY }}
{%- endif +%}
{%+ endfor +%}
Méthode 2
- Mettre des
-systématiquement sauf aux extrémités de la boucle - A chaque fin de ligne utiliser :
{{ '\n' }}
{% for KEY in CONF.SYS_GROUPS -%}
{%- if CONF.SYS_GROUPS[KEY].GID is defined -%}
RUN groupadd -g {{ CONF.SYS_GROUPS[KEY].GID }} {{ KEY + '\n' }}
{%- else -%}
RUN groupadd {{ KEY + '\n' }}
{%- endif -%}
{%- endfor %}
Ansible Templating all j2 files
- name: Templating all j2 files with config.yaml gather_facts: false hosts: localhost tasks: - name: Include vars of config.yaml no_log: true ansible.builtin.include_vars: file: config.yaml name: CONF - name: Find all j2 templates files register: reg_find_j2tpls ansible.builtin.find: paths: "." patterns: "*.j2" hidden: false file_type: file recurse: true excludes: - .git - name: Templating j2 files ansible.builtin.template: src: "{{ item }}" dest: "{{ dest }}" mode: "0600" loop: "{{ reg_find_j2tpls.files | map(attribute='path') }}" vars: dest: "{{ item | regex_replace('\\.j2$', '') }}"
Pb
Err template error while templating string: no filter named 'split'
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: no filter named 'split'. String: {{ fic_resolv.content | b64decode | split('\n') | select('match', '^nameserver\\\\s' ) | replace('\\\\t', ' ') | regex_replace('nameserver\\\\s+') }}"}
La version d'Ansible est trop ancienne
A la place du filtre split il est possible d'utiliser la méthode split ou splitlines
- name: Set facts - get values - resolv.conf ansible.builtin.set_fact: # resolv_lines_domains: "{{ fic_slurp_resolv.content | b64decode | split('\n') | select('match', '^domain\\s' ) | replace('\\t', ' ') | regex_replace('domain\\s+') }}" resolv_lines_domains: "{{ (fic_slurp_resolv.content | b64decode).split('\n') | select('match', '^domain\\s' ) | list | replace('\\t', ' ') | regex_replace('domain\\s+') }}"
Notes Ansible boucle loop
Exemple équivalent à une boucle for imbriqué dans une boucle for, grâce au filtre product
#! /usr/bin/ansible-playbook - name: play test loop hosts: localhost vars: objects: - pomme - banane emails: - 'root@localhost' - 'plop@acme.fr' tasks: - name: Exemple boucle debug: msg: "{{ object }} - {{ email }}" vars: object: "{{ item.0 }}" email: "{{ item.1 }}" loop: "{{ objects | product(emails) | list }}"
ok: [localhost] => (item=['pomme', 'root@localhost']) => { "msg": "pomme - root@localhost" } ok: [localhost] => (item=['pomme', 'plop@acme.fr']) => { "msg": "pomme - plop@acme.fr" } ok: [localhost] => (item=['banane', 'root@localhost']) => { "msg": "banane - root@localhost" } ok: [localhost] => (item=['banane', 'plop@acme.fr']) => { "msg": "banane - plop@acme.fr" }
Ansible loop break
#!/usr/bin/ansible-playbook --- - name: test hosts: localhost tasks: - name: break 1 command: "echo {{ item }}" register: res with_sequence: 0-100 when: - item |int > 3 - not (res.changed|d(false)) - name: break 2 set_fact: id: "{{ item }}" register: res2 with_sequence: 0-100 # Because set_fact not make "change" changed_when: item |int > 3 when: - not (res2.changed|d(false)) # Ca fonctionne mais # * hélas ce n'est pas un vrai Break car il boucle sur toutes les valeurs # * plus problématique encore, ce n'est pas idempotent, car il fait un "Change" à chaque appel # Nous préférerons utiliser les listes - name: break 3-A set_fact: ids: "{{ ids | d([]) + [ item | int ] }}" with_sequence: 0-100 when: item |int > 3 - name: break 3-B debug: msg: "{{ ids | min }}"
Notes Ansible ansible-navigator
Config
ansible.cfg
# Since Ansible 2.12 (core): # To generate an example config file (a "disabled" one with all default settings, commented out): # $ ansible-config init --disabled > ansible.cfg # # Also you can now have a more complete file by including existing plugins: # ansible-config init --disabled -t all > ansible.cfg # # Voir la configuration : # ansible-config dump --only-changed -t all # For previous versions of Ansible you can check for examples in the 'stable' branches of each version # Note that this file was always incomplete and lagging changes to configuration settings # for example, for 2.9: https://github.com/ansible/ansible/blob/stable-2.9/examples/ansible.cfg [galaxy] server_list = rh-certified_repo, published_repo, community_repo, galaxy, dev-community [galaxy_server.rh-certified_repo] token=000000000000000 url=https://aap.acme.local/api/galaxy/content/rh-certified/ [galaxy_server.published_repo] token=000000000000000 url=https://aap.acme.local/api/galaxy/content/published/ [galaxy_server.community_repo] token=000000000000000 url=https://aap.acme.local/api/galaxy/content/community/ [galaxy_server.galaxy] url=https://galaxy.ansible.com/ [galaxy_server.dev-community] url=https://aah.acme.local/api/galaxy/content/community/ token=000000000000000
~/.ansible-navigator.yml
# # cspell:ignore cmdline, workdir --- ansible-navigator: ansible: config: path: ./ansible.cfg execution-environment: container-engine: podman enabled: true image: image-standard:0.9.1 logging: level: warning
Exemple
Lancer un playbook
ansible-navigator --ee false –m stdout playbook.yml –l server01
Afficher les infos sur une image
ansible-navigator --eei ADRESSE_HUB/EE:X.Y.Z images -d
Lancer un playbook dans un Execution Environment donné
podman images podman login --tls-verify=false aap.acme.local -u jean@acme.local
ansible-navigator -m stdout run play.yml -i localhost, -c local -vv --eei aap.acme.local/image-standard:0.9.1
inv.txt
localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python
ansible-navigator -m stdout run -i inv.txt --ee true --eei acme-automationhub-prod.admin.acme.local/acme-opn_lnx-recommended:0.9.4 /home/admin/play-mssql.yml
Lister les images
ansible-navigator -m stdout images | egrep '\sname:' |egrep -v '/|overlay' |awk '{print $2}'
Avoir les informations sur une image
ansible-navigator -m stdout --eei localhost/plop images -d python_version ansible-navigator -m stdout --eei localhost/plop images -d ansible_version
Notes Ansible - Validate - file name extension
Le “validate” d'Ansible ne fonctionne pas si la commande de validation est tributaire de l’extension (la fin du nom de fichier après le point communément de 3 ou 4 caractères).
En effet le fichier poussé par Ansible n'est pas tout de suite nommé de son nom définitif.
La solution est de créer un script “wraper” pour envelopper la commande de validation. Il se chargera de le nommer comme il se doit avant.
Voici un exemple avec une validation d'un fichier Unit de SystemD.
files/wrapper_systemd-analyze_verify.sh
#! /bin/bash # Wrapper for Ansible M(template) A(validate) # Because 'systemd-analyze verify' work only when file is named with correct extention like .service .socket or .timer # Used by R(CDO_SET_SERVICE_FILE) FICNAME="$1" get_type() { if [[ "$(cat $FICNAME)" =~ '[Service]' ]] then echo '.service' elif [[ "$(cat $FICNAME)" =~ '[Socket]' ]] then echo '.socket' elif [[ "$(cat $FICNAME)" =~ '[Timer]' ]] then echo '.timer' else echo "ERROR getting type" exit 3 fi } NEW_FICNAME="$(basename $FICNAME)$(get_type)" ln -fs "$FICNAME" "$NEW_FICNAME" systemd-analyze verify "$NEW_FICNAME" RET=$? unlink "$NEW_FICNAME" exit $RET
- name: hack - wrapper to systemd-analyze_verify for validate become: true become_user: "{{ becomeuser }}" when: unit_file_comp.changed block: - name: make dir into .ansible/tmp-files/ file: path="{{ homedir }}/.ansible/tmp-files/" state=directory recurse=true - name: copy script for validate wrapper_systemd-analyze_verify.sh copy: src: wrapper_systemd-analyze_verify.sh dest: "{{ homedir }}/.ansible/tmp-files/" mode: 0755 - name: template systemd unit file template: src: systemd-unit.j2 dest: "{{ systemd_file_path }}" validate: "{{ homedir }}/.ansible/tmp-files/wrapper_systemd-analyze_verify.sh %s"
Notes Ansible - test ports de connexion SSH
Voir aussi :
- block / rescue
Context :
Le port par défaut pour SSH est le 22.
Nous changeons habituellement ce port en 2222.
Nous écrivons un playbook capable d’adresser les machines avec SSH configuré avec le port 2222 ainsi que celles configurées avec le port 22.
wrapper_ssh_ports.yml
--- - name: wrapper_both_ssh_ports hosts: all gather_facts: false vars: port_test_1: 2222 port_test_2: 22 tasks: - name: set_fact port_test_1 set_fact: ansible_port: "{{ port_test_1 }}" - name: ping 1 ignore_unreachable: true ping: register: ping_1 - name: set_fact port_test_2 when: not ( ping_1.ping is defined and ping_1.ping == 'pong' ) set_fact: ansible_port: "{{ port_test_2 }}" - name: ping 2 when: not ( ping_1.ping is defined and ping_1.ping == 'pong' ) ignore_unreachable: true ping: register: ping_2 - name: fail when all ports NOK assert: that: - ( ping_1.ping is defined and ping_1.ping == 'pong' ) or ( ping_2.ping is defined and ping_2.ping == 'pong' ) msg: "Fail unreachable target" - import_playbook: play2.yml
play2.yml
- name: playbook to launch always - SSH port 2222 and 22 hosts: all gather_facts: false tasks: - name: commande uptime command: uptime changed_when: false register: uptime - name: echo uptime debug: var: uptime.stdout
