Aller au contenu principal

Ecrire du code ansible correctement

Lorsqu'on écrit des playbooks et des rôles Ansible, comme pour tous les langages de programmation, il y a un certain nombre de bonnes pratiques et de règles à respecter pour obtenir un code de qualité. A moins de faire toujours la même chose, il est difficile de connaître toutes ces règles. C'est là qu'interviennent les outils de linting. Ansible-Lint va vous permettre de contrôler le respect de toutes ces règles et d'éviter les pièges courants sur vos playbooks, vos rôles et même les collections. Ansible-lint va également vous aider à mettre à niveau votre code pour qu'il continue de fonctionner lors des sorties des nouvelles versions d'Ansible.

Le projet Ansible Galaxy utilise Ansible-Lint pour calculer les scores de qualité des codes déposés dans le Galaxy Hub.

Installation d'Ansible Lint

Comme Ansible, l'outil ansible-lint est écrit en python et donc son installation se fait via pip ou pix (mon préféré car indépendant de la version d'ansible).

On va l'installer avec yamllint.

pip3 install "ansible-lint[yamllint]"
# ou
pipx installe ansible-lint

Dans le cas où vous utilisez encore d'anciennes versions d'Ansible, il est possible de choisir une version du linter compatible avec celle-ci.

pip3 install ansible-lint "ansible>=2.9,<2.10"

Que contrôle ansible lint?

Par défaut cet outil permet de contrôler en autre :

  • l'utilisation command ou shell plutôt qu'un module existant prenant en charge cette commande
  • l'utilisation command plutôt que shell (si le module équivalent n'existe pas)
  • le bon respect des espaces dans les noms de variables : {{ my_variable }} et non {{my_variable}}
  • la présence de local_action qui doit être remplacé par delegate_to: localhost
  • l'utilisation de modules dépréciés
  • les tests de chaînes de caractères de longueur nulle: when: var|length > 0
  • l'utilisation du nom complet pour les modules ansible.builtin (depuis la version 3.0 d'ansible)
  • l'utilisation de failed_when avec ses conditions d'erreurs plutôt que ignore_errors
  • l'utilisation des FQCN : Full Qualified Collection Names
  • ...

La liste complète avec leurs explications

Pour afficher toutes les règles utilisées par défaut, il suffit de taper la commande suivante :

ansible-lint -v -T
INFO     Identified / as project root due file system root.
# List of tags and rules they cover
command-shell:  # Specific to use of command and shell modules
  - command-instead-of-module
  - command-instead-of-shell
  - inline-env-var
  - no-changed-when
  - risky-shell-pipe
core:  # Related to internal implementation of the linter
  - internal-error
  - load-failure
  - parser-error
  - syntax-check
  - warning
  - schema

Pour limiter les tests sur certaines règles, il suffit d'utiliser l'option -t. Par exemple pour lancer un contrôle sur juste l'idempotence :

ansible-lint -t idempotency playbook.yml

De la même manière pour exclure certaines règles :

ansible-lint -x formatting,metadata playbook.yml

On peut plutôt que de les ignorer les afficher en warning:

ansible-lint -w experimental playbook.yml

Ansible-Lint par la pratique

Prenons ce playbook par exemple :

---
- hosts: all
  gather_facts: no
  tasks:

    - name: this would typically fire deprecated-command-syntax
      command:
        cmd: chmod 644 X

    - name: this would typically fire command-instead-of-module
      command: git pull --rebase

    - name: this would typically fire git-latest
      git:
        repo: http://git.com/test
        dest: /tmp/test

qui retourne :


WARNING  Listing 12 violation(s) that are fatal
name[play]: All plays should be named.
test.yaml:2

yaml[truthy]: Truthy value should be one of [false, true]
test.yaml:3

fqcn[action-core]: Use FQCN for builtin module actions (command).
test.yaml:6 Use `ansible.builtin.command` or `ansible.legacy.command` instead.

name[casing]: All names should start with an uppercase letter.
test.yaml:6 Task/Handler: this would typically fire deprecated-command-syntax

no-changed-when: Commands should not change things if nothing needs doing.
test.yaml:6 Task/Handler: this would typically fire deprecated-command-syntax

command-instead-of-module: git used in place of git module
test.yaml:10 Task/Handler: this would typically fire command-instead-of-module

fqcn[action-core]: Use FQCN for builtin module actions (command).
test.yaml:10 Use `ansible.builtin.command` or `ansible.legacy.command` instead.

name[casing]: All names should start with an uppercase letter.
test.yaml:10 Task/Handler: this would typically fire command-instead-of-module

no-changed-when: Commands should not change things if nothing needs doing.
test.yaml:10 Task/Handler: this would typically fire command-instead-of-module

fqcn[action-core]: Use FQCN for builtin module actions (git).
test.yaml:13 Use `ansible.builtin.git` or `ansible.legacy.git` instead.

latest[git]: Result of the command may vary on subsequent runs.
test.yaml:13 Task/Handler: this would typically fire git-latest

name[casing]: All names should start with an uppercase letter.
test.yaml:13 Task/Handler: this would typically fire git-latest

Read documentation for instructions on how to ignore specific rule violations.

                        Rule Violation Summary
 count tag                       profile    rule associated tags
     1 command-instead-of-module basic      command-shell, idiom
     1 name[play]                basic      idiom
     1 yaml[truthy]              basic      formatting, yaml
     3 name[casing]              moderate   idiom
     1 latest[git]               safety     idempotency
     2 no-changed-when           shared     command-shell, idempotency
     3 fqcn[action-core]         production formatting

Failed after min profile: 12 failure(s), 0 warning(s) on 1 files.

Que faut-il faire pour qu'ansible-lint ne retourne pas d'erreurs ?

  • Mettre des majuscules à la première lettre de la description
  • Ajouter à la ligne 2 name: UMn playbook
  • A la ligne 3 remplacer no par false
  • A la ligne 9 on doit ajouter changed_when pour indiquer à Ansible quand la commande utilisée va réellement changer quelque chose sur votre machine cible.
  • A la ligne 10 remplacer le module git par son propre module !!!
  • Pour que l'on respecte les règles d'idempotence, il est important de ne pas utiliser default mais de nommer une version explicite.

Si on souhaite forcer certaines règles, il est possible d'ajouter des arguments au playbook :

    - name: this would typically fire git-latest
      git:
        repo: http://git.com/test
        dest: /tmp/test
      args:
        warn_list: false
  • Pour les autres modules, on peut skipper le test en ajoutant le tag skip_ansible_lint :
    - name: this would typically fire git-latest
      git:
        repo: http://git.com/test
        dest: /tmp/test
      tags:
        - skip_ansible_lint

On peut, comme l'indique la commande, créer un fichier .ansible-lint contenant par exemple :

# .ansible-lint
warn_list:  # or 'skip_list' to silence them completely
  - yaml  # Violations reported by yamllint

Mais je n'aime pas trop cela, car dans ce cas ce sont toutes les erreurs de ce type qui sont ignorées.

Ansible-lint apporte certaines corrections tout seul

Introduit avec la version 6.0.0, ansible-lint permet désormais d'apporter certaines corrections à votre place avec l'option --write :

  • L'utilisation des FQCN : fqcn
  • La première lettre de la description en capitale : name
  • les valeurs booléenes à true ou false : yaml

Cette option s'enrichit au fil de la sortie des versions.

Utilisation de l'option --write

Cette option prend une valeur qui peut être all, none ou une liste des id de règles à corriger séparés par des virgules.

Si on reprend notre fichier exemple ci-dessus et que nous voulons corriger la capitalisation :

ansible-lint --write none,name test.yaml

Cela nous donne ce résultat :

---
- hosts: all
  gather_facts: false
  tasks:
    - name: This would typically fire deprecated-command-syntax
      command:
        cmd: chmod 644 X

    - name: This would typically fire command-instead-of-module
      command: git pull --rebase

    - name: This would typically fire git-latest
      git:
        repo: http://git.com/test
        dest: /tmp/test
ansible-lint --write all test.yaml
---
- hosts: all
  gather_facts: false
  tasks:
    - name: This would typically fire deprecated-command-syntax
      ansible.builtin.command:
        cmd: chmod 644 X

    - name: This would typically fire command-instead-of-module
      ansible.builtin.command: git pull --rebase
    - name: This would typically fire git-latest
      ansible.builtin.git:
        repo: http://git.com/test
        dest: /tmp/test

ansible-lint a corrigé les fqcn manquants !!!

Écrire ses propres règles Ansible-Lint

Il est possible de créer ses propres règles et de les utiliser avec l'option -r /path/to/custom-rules.

Pour la syntaxe des règles il suffit de se rendre sur cette page : custom-rules

Si on veut les utiliser conjointement avec les règles par défaut, on remplace -w par -W.

La suite

Il ne vous reste plus qu'à l'intégrer à votre ci !!

Deux alternatives ansible-later et spotter, qui est celui qui donne le plus de recommendations sur l'amélioration de votre code Ansible.