Infrastructure at your Service

Joël Cattin

Scanning Ansible code with Ansible Lint

Introduction

It’s always good to learn something new. But doing it the right way from the very beginning is even better.
I recently started to work with Ansible and after creating a few roles, I quickly realized that there were many different ways to achieve the same goal.
Some examples are :
– The use of the Shell module vs the Command module
– The use of Loop vs With_*
– The use of an Handler vs a Task when it comes to run operations on change

On one side, having such liberty is pretty fun, as it leaves room for everyone’s imagination. But on the other, having some best practices or guidelines can also become very important, especially when working in a shared development environment (using git for example) and when you want to guarantee code uniformity.

In the Ansible world, Ansible Lint is the tool to start with when you want to check your Playbooks and Roles code under various aspects.
Its goal is also to help developpers to write their code in a way that it remain compatible with newer versions of Ansible. To achive this Ansible Lint will, for instance, check the use of deprecated modules or commands.

Installation

Ansible Lint is not part of the Ansible package, so you have to install it on your system. If you’re working on a Debian/Ubuntu based OS you can use the apt command :

apt install ansible-lint
As it’s a Python application you can also use Pip to install it :

pip3 install ansible-lint
Once installed, you should be able to check the version :

[email protected]:~$ ansible-lint --version 
ansible-lint 5.0.7.dev3 using ansible 2.9.6 
[email protected]:~$

 

Default rules

By default, Ansible Lint will run based on a set of default rules. The complete list can be found in the official documentation : https://ansible-lint.readthedocs.io/en/latest/default_rules.html
You can also use the command ansible-lint -L to list them directly in your console :

Screenshot truncated – more than 40 rules exist by default

Ansible Lint can be run against a playbook. In that case all underlying roles will be scanned as well.
You can also run it directly against a specific role :

[email protected]:/ansible$ ansible-lint roles/ora_rdbms_getmospatch/*
WARNING  Listing 1 violation(s) that are fatal
deprecated-local-action: Do not use 'local_action', use 'delegate_to: localhost'
roles/ora_rdbms_getmospatch/tasks/combo.yml:39   local_action:
[email protected]:/ansible$ ansible-lint roles/ora_rdbms_getmospatch/*

The output above is quite clear : The keyword local_action is deprecated and delegate_to must be used instead. This is one of the Ansible Lint default rules.
Unfortunately, in that case the delegation is done on a per-task basis (see below), thus I have no other choice to use local_action. We can consider this Warning as a false positive.

Skipping a rule

If you don’t want Ansible Lint to inform you again about a violation the next time you run the checks, you can add the related rule in the skip_list section of the configuration file :

[email protected]:~/ansible$ cat .ansible-lint
skip_list:
- deprecated-local-action
[email protected]:~/ansible$
Or you can use the -x argument of the command line :

[email protected]:/ansible$ ansible-lint roles/ora_rdbms_getmospatch/* -x deprecated-local-action
[email protected]:/ansible$
As you can imagine, the drawback of using the skip_list or -x is that the rule will be skipped for all your playbooks/roles/tasks.
Hopefully, if the rule is line-based, you can disable it for a single task by placing # noqa [rule_id] at the end of the particular line you want to be skipped :

- name: Copy combo patch to repository
  become: true
  become_user: ansible
  local_action: # noqa deprecated-local-action
  module: ansible.builtin.copy
    src: "{{ rv_ora_rdbms_getmospatch_tmp_folder }}/{{ pv_ora_patch_list.combo[combo_patch_id].patch_archive | basename }}"
    dest: "{{ pv_ora_patch_list.combo[combo_patch_id].patch_archive }}"
    owner: ansible
    group: ansible
  loop: "{{ pv_ora_patch_list.combo | list }}"
  loop_control:
    loop_var: combo_patch_id
    label: "{{ combo_patch_id }} - {{ pv_ora_patch_list.combo[combo_patch_id].desc }}"
You can also use the tag skip_ansible_lint :

- name: Copy combo patch to repository
  become: true
  become_user: ansible
  local_action: # noqa deprecated-local-action
  module: ansible.builtin.copy
    src: "{{ rv_ora_rdbms_getmospatch_tmp_folder }}/{{ pv_ora_patch_list.combo[combo_patch_id].patch_archive | basename }}"
    dest: "{{ pv_ora_patch_list.combo[combo_patch_id].patch_archive }}"
    owner: ansible
    group: ansible
  loop: "{{ pv_ora_patch_list.combo | list }}"
  loop_control:
    loop_var: combo_patch_id
    label: "{{ combo_patch_id }} - {{ pv_ora_patch_list.combo[combo_patch_id].desc }}"
  tags:
    - skip_ansible_lint

 

Integration with pre-commit

Last but not least, Ansible Lint can also be integrated to pre-commit. Very useful if you want to check you code automatically before pushing it to your git repository.
To do that, just update your local .pre-commit-config.yaml file with the following :

[email protected]:~/ansible$ cat .pre-commit-config.yaml 
repos:
- repo: https://github.com/ansible-community/ansible-lint.git
  rev: v5.1.3
  hooks:
    - id: ansible-lint
      files: \.(yaml|yml)$
[email protected]:~/ansible$

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Joël Cattin
Joël Cattin

Senior Consultant