Notes Ansible Jinja2

Voir :

https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-control-structures

https://ansible-arista-howto.readthedocs.io/en/latest/JINJA_YAML_STRUCTURES.html

https://tedboy.github.io/jinja2/templ14.html

https://docs.saltproject.io/en/latest/topics/jinja/index.html

https://blog.stephane-robert.info/post/ansible-template-jinja/

j2cli

Mastering loops with Jinja templates in Ansible

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 :

https://pypi.org/project/djlint/

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

Voir : https://jinja.palletsprojects.com/en/stable/templates/#variables

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+') }}"