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
ansible: install python3
# Assuming a inventory file at ./inventory
ansible-inventory -i ./inventory --list
Convert an inventory file to a JSON format

Common structures

# Simple loops
- name: Ensure some OS packages are installed.
  apt: name="{​{ item }​}" state=latest
  loop:
    - git
    - golang
    - python3-pip
    - jq
  become: true
ansible: install multiple packages using apt

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
ansible: install from GitHub release and extract all files

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
ansible: install from GitHub release and extract only specific files

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"
ansible: how to check the distro family?

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
ansible: how to ensure a single text line in present in a file?
# 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"
ansible: how to ensure a single text line in present in a file?

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"]
ansible: how to raise a helpful error message and then fail
- name: Assert "gather_facts" was run
  ansible.builtin.assert:
    that:
      - ansible_facts is defined
ansible: assert conditional, in a test language style

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
ansible: check for installed package and raise an error