Linting your Ansible Playbooks and make a Continuous Integration(CI) Solution

Abhijeet Kamble
Jan 18, 2019 · 8 min read

First thing first, if you are not aware about linting, let me help you with its definition which i am copying from wiki.

Linting: It is the process of running a program that will analyse code for potential errors. lint scans your code against some rules and provides you a nice analysis report.

Reason for the blog / what problems we are facing

We have implemented Ansible as a deployment tool for our Organisation, and we are successfully helping development teams to deploy their code using Ansible. Not only to deploy, we are also helping teams to configure their systems(any environment). As the development team is not fully aware about Ansible Playbooks, they face lot of issues related to syntax errors, or sometime using a deprecated module, which breaks when we enhance the Ansible versions. We wanted a solution through which development teams should adapt Ansible quickly and also solve the issues on their own without triggering a panic button and running to catch us..!!

Who should read this

This blog is specifically for those who works and interacts with Ansible in day to day life.

- Those who are struggling with ansible yaml syntax check.

- Those who want to implement continuous integration solution which should check against some rules.

- Those who are looking to check against some practices.

What is Ansible lint?

Ansible lint is a linting solution provided by Ansible to lint your playbooks. Ansible lint is basically a command line utility. This was initially being used to scan ansible galaxy projects to check their quality score. Thanks to @willthames for starting this project and Ansible team to keep on updating the project.

How to Install

To install Ansible-Lint you can use pip, its a simple command which will install Ansible-lint on your system.

pip install ansible-lint

or if you want to install it from the source, you can run the below command.

pip install git+https://github.com/ansible/ansible-lint.git

To check if the installation is done and ansible lint is present, you can run a command as

ansible-lint --version
ansible-lint 3.4.15 (oputput)

This states that Ansible lint is installed and prints the version which is installed on your system.

How to use it

Well the next thing which comes to anyones mind is how we can use it and how it will help us become productive.

The usage is very easy, if you type ansible-lint help it will print with all options.

ansible-lint --help
Usage: ansible-lint playbook.yml
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-L list all the rules
-q quieter, although not silent output
-p parseable output in the format of pep8
-r RULESDIR specify one or more rules directories using one or
more -r arguments. Any -r flags override the default
rules in /usr/local/lib/python2.7/dist-
packages/ansiblelint/rules, unless -R is also used.
-R Use default rules in /usr/local/lib/python2.7/dist-
packages/ansiblelint/rules in addition to any extra
rules directories specified with -r. There is no need
to specify this if no -r flags are used
-t TAGS only check rules whose id/tags match these values
-T list all the tags
-v Increase verbosity level
-x SKIP_LIST only check rules whose id/tags do not match these
values
--nocolor disable colored output
--force-color Try force colored output (relying on ansible's code)
--exclude=EXCLUDE_PATHS
path to directories or files to skip. This option is
repeatable.

These are all the options which are provided by Ansible Lint using which you can see what all rules are present by-default or if you have any custom rules which you want to check against any playbook and all. Lets start with a simple one.

First we will see what all rules are present (this is the most important thing, as you should know what all rules are present against which you are going to scan your playbook).

ansible-lint -L
ANSIBLE0002: Trailing whitespace
There should not be any trailing whitespace
ANSIBLE0004: Git checkouts must contain explicit version
All version control checkouts must point to an explicit commit or tag, not just "latest"
ANSIBLE0005: Mercurial checkouts must contain explicit revision
All version control checkouts must point to an explicit commit or tag, not just "latest"
ANSIBLE0006: Using command rather than module
Executing a command when there is an Ansible module is generally a bad idea
ANSIBLE0007: Using command rather than an argument to e.g. file
Executing a command when there is are arguments to modules is generally a bad idea
ANSIBLE0008: Deprecated sudo
Instead of sudo/sudo_user, use become/become_user.
ANSIBLE0009: Octal file permissions must contain leading zero
Numeric file permissions without leading zero can behavein unexpected ways. See http://docs.ansible.com/ansible/file_module.html
ANSIBLE0010: Package installs should not use latest
Package installs should use state=present with or without a version
ANSIBLE0011: All tasks should be named
All tasks should have a distinct name for readability and for --start-at-task to work
ANSIBLE0012: Commands should not change things if nothing needs doing
Commands should either read information (and thus set changed_when) or not do something if it has already been done (using creates/removes) or only do it if another check has a particular result (when)
ANSIBLE0013: Use shell only when shell functionality is required
Shell should only be used when piping, redirecting or chaining commands (and Ansible would be preferred for some of those!)
ANSIBLE0014: Environment variables don't work as part of command
Environment variables should be passed to shell or command through environment argument
ANSIBLE0015: Using bare variables is deprecated
Using bare variables is deprecated. Update your playbooks so that the environment value uses the full variable syntax ("{{your_variable}}").
ANSIBLE0016: Tasks that run when changed should likely be handlers
If a task has a `when: result.changed` setting, it's effectively acting as a handler
ANSIBLE0017: become_user requires become to work as expected
become_user without become will not actually change user
ANSIBLE0018: Deprecated always_run
Instead of always_run, use check_mode.

So, these are all the default rules against which it is going to scan your playbook.

Let’s Scan our first playbook to see the output(I am sure there will be some issues in my playbook :P)

ansible-lint site.yml
[ANSIBLE0011] All tasks should be named
site.yml:6
Task/Handler: include_tasks print.yml
[ANSIBLE0011] All tasks should be named
site.yml:11
Task/Handler: include_tasks print.yml

after the scan, you can see Ansible-Lint has pointed out few of my tasks are declared without any name. So this was a sample scan where we have scanned a single playbook. You can also scan multiple playbooks or even custom roles as well. Let’s see how you can do that.

ansible-lint *.yml
(this will scan all your playbooks present at current location)

If you are looking for scanning a custom role, you can achieve it by doing this, this is one of the best thing and I was looking for as we deal with creating lots of roles (generic) which is being used by our organisation.

ansible-lint roles/website-iis-creation/
(here it will scan the main.yml file, if you have multiple files within your roles I would recommend to use *.yml while scanning)

How to configure it

Any CLI tool will always provides a configuration option, Ansible-lint also provides one. Ansible-lint supports local configuration file called .ansible-lint . Ansible-lint checks the initial working directory and tries to find the file and applies any configuration present.

If you want to override the configuration file, you can do that with command line option -c /path/to/file/.

Interestingly, if you provide the value on command line and value present in configuration file. Ansible-Lint will merge the options and scan against them.

These are the following things supported by Ansible-Lint Configuration.

exclude_paths:
- ./my/excluded/directory/
- ./my/other/excluded/directory/
- ./last/excluded/directory/
parseable: true
quiet: true
rulesdir:
- ./rule/directory/
skip_list:
- skip_this_tag
- and_this_one_too
- skip_this_id
- '401'
tags:
- run_this_tag
use_default_rules: true
verbosity: 1

Integrating the same in CI servers for your playbook( Jenkins and Bamboo and Circle CI)

Many of you might ask me why we need a CI solution for Ansible Playbooks. let me explain the problems that we faced, we have implemented Ansible as deployment tool across our organisation and because of which the development team has to regularly use Ansible in their day to day activities, they faced lot of issues while adapting it as we have defined lot of rules and we used custom solution to scan this (prior to Ansible-Lint) and that can only be identified at the time of Deployment when the checks fails. Second issue is the syntax issue, as most of the development teams are not aware about the YAML syntax.

So, two problems mainly.

  1. YAML Syntax Issues.
  2. Scan against Custom Rules.

We achieved all this with Ansible-Lint, I would like to thanks Ansible team and @willthames for creating this project. With all above features, Ansible-Lint also provides syntax checking as well. Now you might ask ansible itself also provides a solution for syntax check with Ansible CLI by--syntax-check .But if you are including custom roles using requirement.txt file, it will break as the roles are not downloaded, so you have to first download all roles and then run syntax check. If you do without that, you will get following error.

But if we use Ansible-lint to detect the syntax, you will get actual syntax error rather than the above where it just as you to download the role first.

So, I Integrated this with Bamboo and Jenkins where I installed Ansible-lint on both the agents and then ran the CI for Ansible Lint(Note: you can also use molecule to detect and test your roles on different platforms)

You can also check against any custom rules and predefined rules which are present in Ansible. The below image shows you how it will detect and let you know the standards that you are not following in your playbook. If you want to scan against any custom rules, that is also easy you just have to specify the custom rules directory using -R and ansible-lint will scan against them as well.

Same way if you have any Syntax issue, it can easily cached with Ansible-lint without downloading any custom roles.

The same is being done using Bamboo as CI server, you can also use any tool in market specially Jenkins and Circle CI as well. I have setup the same stuff with Jenkins as well, below is the screenshot of the successful build.

I guess that’s all, with this blog I just wanted to share that you should scan your playbook against the Ansible standards and if you have any you should create one and share with other folks. This was my story and how we achieved and fixed our internal issue. Let me know your thoughts and Happy Reading..!!

Join our community Slack and read our weekly Faun topics ⬇

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

Faun

The Must-Read Publication for Aspiring Developers & DevOps…

Abhijeet Kamble

Written by

DevOps Consultant, ALM Consultant, CloudOps Consultant

Faun

Faun

The Must-Read Publication for Aspiring Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade