Ansible tricks
I want to show you some tricks and solutions that I designed along my journey automating stuff with Ansible. To use these tricks doesn't matter if you are new in the Ansible world or already are a power user.
How to learn Ansible?
If you want to learn a lot of stuff from a really competent person, Jeff Geerling has an awesome playlist on his YouTube channel. The geerlingguy
(his GitHub/Twitter name account) was part of the Ansible team and still maintaining many Ansible roles and collections. This is the best source that I can recommend for any beginner and even more advanced users today (2021).

Free Ansible Workshops available on GitHub:
Instruqt - Self-paced and interactive lab training:

Free Red Hat introduction to Ansible and Automation Controller (AWX):

A good reference to an article from my work college John Westcott:

Basics
# Ansible requires python3 on the machine in order to work,
# but does exist one trick to install python3 with ansible:
- name: Bootstrap a host without python3 installed
raw: dnf install -y python3
# Assuming a inventory file at ./inventory
ansible-inventory -i ./inventory --list
Common structures
# Simple loops
- name: Ensure some OS packages are installed.
apt: name="{{ item }}" state=latest
loop:
- git
- golang
- python3-pip
- jq
become: true
Install binary from remote
A common problem that I face is installing the latest version of an application from a GitHub release, so here are two examples of how to do that.
Install binary from GitHub release:
# sops
- name: Get latest 'sops' release metadata
uri:
url: https://api.github.com/repos/mozilla/sops/releases
return_content: yes
register: _sops_github_releases
failed_when: "'.linux' not in _sops_github_releases.content"
- name: Ensure latest 'sops' release is installed
get_url:
url: "{{ item.browser_download_url }}"
dest: /usr/local/bin/sops
mode: 0755
loop: "{{ _sops_github_releases.json[0].assets}}"
when: "'.linux' in item.name"
become: true
Extract specific files from remote:
# age
- name: Get latest 'age' release metadata
uri:
url: https://api.github.com/repos/FiloSottile/age/releases
return_content: yes
register: _age_github_releases
failed_when: "'linux-amd64.tar.gz' not in _age_github_releases.content"
- name: Ensure latest 'age' release is installed
ansible.builtin.unarchive:
src: "{{ item.browser_download_url }}"
dest: /usr/local/bin
extra_opts:
- --strip=1
- --wildcards
- '*/age*'
mode: 0755
remote_src: yes
loop: "{{ _age_github_releases.json[0].assets }}"
when: "'linux-amd64' in item.name"
become: true
Conditionals
How to create more complex and host specific checks.
# requires "ansible_facts" enabled
# Conditional for "Ubuntu" only
#
# ansible_facts['distribution'] = [Amazon, CentOS, Fedora, Ubuntu, ...]
- name: Install using apt
apt:
name: htop
state: latest
when: ansible_facts['distribution'] == "Ubuntu"
# You can also defined for OS family
#
# ansible_facts['os_family'] = [RedHat, Debian]
- name: Install using apt
dnf:
name: htop
state: latest
when: ansible_facts['distribution'] == "RedHat"
Content in file
Ansible offers two major ways to ensure that a text content is present in a file.
# https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html
- name: Ensure bash_autocomplete is enabled
ansible.builtin.lineinfile:
path: ~/.bashrc
regexp: '^source /usr/share/bash-completion/bash_completion'
line: source /usr/share/bash-completion/bash_completion
# https://docs.ansible.com/ansible/latest/collections/ansible/builtin/blockinfile_module.html
- name: Add multiple lines to "~/.bashrc"
ansible.builtin.blockinfile:
path: ~/.bashrc
marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
block: |
source <(kubectl completion bash)
alias k="kubectl"
Error handling
- name: Check for compatible operating system
fail:
msg: >-
You are using an incompatible operating system.
Supported operating systems are: [Fedora, Debian, Ubuntu]
when: not ansible_facts['distribution'] in ["Fedora", "Debian", "Ubuntu"]
- name: Assert "gather_facts" was run
ansible.builtin.assert:
that:
- ansible_facts is defined
Check for installed package
Sometimes you may want to check if a package is installed and raise a proper error, without actually installing something or needing root access.
- name: Define required package name
set_fact:
package_name_check: "htop"
- name: Check "package_name_check" var exist
fail:
msg: "{{ package_name_check }} is not defined"
when: package_name_check is not defined
- name: Search for the package in the DNF repos
dnf:
list: "{{ package_name_check }}"
register: result
- name: Assert that the package is actually installed
vars:
is_package_installed: "{{ 'installed' in (result.results | map(attribute='yumstate') | list) }}"
fail:
msg: "The package '{{ package_name_check }}' is not installed!"
when: is_package_installed is not true