Outils pour utilisateurs

Outils du site


blog

Notes Ansible plugins

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

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

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

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
blog.txt · Dernière modification : de 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki