Outils pour utilisateurs

Outils du site


blog

Notes Ansible map select reject

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
2025/03/24 15:06

Notes Ansible Jinja2

Voir :

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+') }}"
2025/03/24 15:06

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 }}"
2025/03/24 15:06

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
2025/03/24 15:06

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"
2025/03/24 15:06
blog.txt · Dernière modification : de 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki