Table des matières
0 billet(s) pour février 2026
Notes Ansible plugins
Créer ses plugins
Voir :
Hello world
Plugin simple en bash et dans un rôle
play-test-plugin.yml
#! /usr/bin/env ansible-playbook - name: test plugin hosts: localhost roles: - plugin - hello
roles/plugin/library/plug1
#! /bin/bash display="This is a simple bash module.." echo -e "{\"Message\":\""$display"\"}"
roles/hello/tasks/main.yml
--- - name: test plugin plug1 plug1:
./play-test-plugin.yml -v
Tester un module
echo -e '{ "ANSIBLE_MODULE_ARGS": { "database": "hosts"} }' | python3.6 /usr/lib/python3.6/site-packages/ansible/modules/system/getent.py |jq . echo -e '{ "ANSIBLE_MODULE_ARGS": {} }' | python3 roles/plop/library/plop_check
Notes Ansible module set_stats
Voir :
env ANSIBLE_SHOW_CUSTOM_STATS=yes ./playbook.yml -i test-ansible,test-ansible2,
Pour ne pas systématiquement devoir mettre ANSIBLE_SHOW_CUSTOM_STATS=yes il est possible de mettre ce fichier ansible.cfg à la racine du projet (même arborescence que le playbook)
ansible.cfg
[defaults] show_custom_stats = True
playbook.yml
#!/usr/bin/ansible-playbook --- - name: play hosts: all tasks: - name: set stats set_stats: data: var1: plop
CUSTOM STATS: ***********************************************************************************************************************************************
RUN: { "var1": "plopplop"}
playbook.yml
#!/usr/bin/ansible-playbook --- - name: play hosts: all tasks: - name: set stats set_stats: data: var1: plop aggregate: no
CUSTOM STATS: ***********************************************************************************************************************************************
RUN: { "var1": "plop"}
Si aggregate: no sur plusieurs machines, la variable est écrasée, c'est la dernière valeur qui l'emporte.
playbook.yml
#!/usr/bin/ansible-playbook --- - name: play hosts: all tasks: - name: set stats set_stats: data: var1: plop per_host: yes
CUSTOM STATS: ***********************************************************************************************************************************************
test-ansible: { "var1": "plop"}
test-ansible2: { "var1": "plop"}
Filtrer le set_stats
env ANSIBLE_SHOW_CUSTOM_STATS=yes ./playbook.yml -i inv.yaml | sed -n -e '/CUSTOM STATS:/,/$/p' | sed -e '/CUSTOM STATS:/d' | sed -e 's/[a-zA-Z0-9]*://' | jq .
Notes Ansible module raw
Le module raw permet de passer des commandes en SSH directement sans avoir besoin de Python installé sur la cible.
En général se module est justement utilisé pour installer Python. Il est aussi utile quand la cible contient une version de Python obsolète.
Exemple de déploiement d'un script shell avec Raw
Sur une vielle RedHat 5 (Python obsolète) les fichiers crées par raw sont systématiquement tronqués à 6258 bytes. De plus certains caractères spéciaux du script shell empêche son déploiement via un heredoc.
Pour contourner ces deux limitations nous allons :
- Découper le fichier en paquets de 6258 bytes
- Encoder le fichier en base64
play-deploy-shell-old-linux.yml
#!/usr/bin/ansible-playbook --- - hosts: all gather_facts: false environment: PATH: /bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/local/cmcluster/bin/ tasks: - name: "Gather facts" ignore_unreachable: true block: - name: "Gather facts for RHEL > 5" ansible.builtin.setup: - name: set_fact python_value ansible.builtin.set_fact: python_value: auto_legacy rescue: - name: set_fact with_raw_module ansible.builtin.set_fact: with_raw_module: true - name: block when with_raw_module when: with_raw_module is defined and with_raw_module block: - name: DEBUG ansible.builtin.debug: var: item with_items: - "{% for host in hostvars %}{{ host }}{% endfor %}" - name: Split file ansible.builtin.shell: | cat script.sh | gzip | openssl base64 | split -b 6258 --additional-suffix .asc - script- args: chdir: files delegate_to: localhost - name: Find ansible.builtin.find: file_type: file paths: files/ patterns: 'script-*' register: f delegate_to: localhost - name: Slurp ansible.builtin.slurp: src: "{{ item }}" register: slurp_shell_code delegate_to: localhost with_items: - "{{ f.files | map(attribute='path') |list }}" - name: Copy shell script ansible.builtin.raw: | cat > script.sh.asc.{{ ansible_loop.index0 }} <<-EOF {{ item.content | b64decode }} EOF args: executable: /bin/bash loop_control: extended: true with_items: - "{{ slurp_shell_code.results }}" - name: Mkdir /usr/local/plop/ ansible.builtin.raw: sudo install -d -m 750 /usr/local/plop/ - name: Merge file ansible.builtin.raw: cat script.sh.asc.* | openssl base64 -d | gzip -d > script.sh - name: Clean temp files ansible.builtin.raw: rm -f script.sh.asc.* - name: Mv shell script ansible.builtin.raw: sudo mv script.sh /usr/local/plop/script.sh - name: Launch script ansible.builtin.raw: sudo bash /usr/local/plop/script.sh
Notes Ansible map select reject
Map reduce filter avec Ansible
Voir :
selectattr
Liste des filtres Jinja2
Développer ses propres filtres :
select (filter)
select l'équivalent Ansible / Jinja à filter de Python
Exemple : Enlever les éléments vides d'une liste
# liste: {{ liste1 | difference(['']) }} # ou liste: {{ liste1 | select | list }} # Ce qui est équivalent à liste: {{ liste1 | select('!=', '') | list }} # et à liste: {{ liste1 | reject('==', '') | list }}
Faire un grep
- name: assert ansible-lint duplicate dict key assert: that: - ansible_lint_ref.rc != 1 msg: "{{ ansible_lint_ref.stderr_lines | select('regex', 'duplicate' ) | list }}" # Show only lines with "duplicate" keywork, because error msg is too much verbose
Nettoyer une liste des éléments undef
msg: '{{ foo | select("defined") }}'
Voir aussi
select('match', 'plop')- selectattr, rejectattr
map
Émuler any et all
- hosts: localhost tasks: - assert: that: - mixed | any - not (mixed | all) - all_true | any - all_true | all - not (all_false | any) - not (all_false | all) vars: mixed: - false - true - false all_true: - true - true - true all_false: - false - false - false
Sur ma version d'Ansible je n'ai pas any ni all
Solution :
vars: any: my_list | map('bool') | max all: my_list | map('bool') | min
Faire un sed
- name: Copy a glob of files based on a list of groups copy: src: "{{ item }}" dest: "/tmp/{{ item }}" loop: '{{ q("fileglob", *globlist) }}' vars: globlist: '{{ mygroups | map("regex_replace", "^(.*)$", "files/\1/*.conf") | list }}'
map attribute
# La ligne ci-dessous : mounts: "{{ ansible_mounts | map(attribute='mount') |list }}" # équivaut à : mounts: "{{ ansible_mounts | json_query('[*].mount') }}"
Autres
A tester
- name: Convert shell: python -c "print [x for b in {{ servers }}['servers']['results'] for x in b['tagged_instances']]" register: my_list_of_dicts
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+') }}"
