How to structure Ansible roles and playbooks for AWX

How I use a mono repo and well-structured folders to elegantly run playbooks in AWX

Output of tree command showing a playbook and various tasks in a role for running infrastructure automation

I want to show you how I lay out my yaml files for working with Ansible at scale with AWX. Let’s begin.

The mono repo

All my code lives in the config/ansible repo.

I think even for large firms, multiple repos is overkill. It’s almost exclusively the IT department writing roles.

Layout

  • We have a simple playbook called server_setup, which
  • has a single role called whoami which
  • has two tasks to report the hostname and ip_address of a server.
  • ansible.cfg is essential for AWX to know to expect roles in the /roles folder.
├── ansible.cfg
├── playbooks
│   └── server_setup.yaml
└── roles
    └── whoami
        └── tasks
            ├── hostname
            │   ├── main.yaml
            │   └── RedHat.yaml
            ├── ip_address
            │   ├── main.yaml
            │   └── RedHat.yaml
            └── main.yaml

Let’s look at these files, starting with ansible.cfg. The sole purpose here is to make the above layout work.

[defaults]
roles_path = /runner/project/roles:/etc/ansible/roles:/usr/share/ansible/roles
ansible.cfg

Moving on, we have our server_setup.yaml file, which lays the groundwork to call the whoami role.

---
- name: Include whoami tasks
  hosts: all
  roles:
    - whoami
playbooks/server_setup.yaml

Inside whoami, we start to get into the thick of it. Everything essential is in a folder called tasks. The entry point is /roles/whoami/tasks/main.yaml which specifies our two actual tasks for showing the host’s hostname or IP:

---
- include_tasks: hostname/main.yaml
  when: echo_hostname | lower == 'yes'

- include_tasks: ip_address/main.yaml
  when: echo_ip_address | lower == 'yes'
roles/whoami/tasks/main.yaml

As you can see on lines 2 and 5, we specify yet another entry point to printing the hostname or IP. The reason is because we want to support a good layout if we have multiple OSes in the environment. Anyways, here’s roles/whoami/tasks/hostname/main.yaml:

---
- name: Echo server hostname
  ansible.builtin.include_tasks: "{{ item }}"
  with_first_found:
    - "{{ ansible_os_family }}.yaml"
  
roles/whoami/tasks/hostname/main.yaml

We use ansible_os_family to choose a file like RedHat.yaml where the actual logic happens! Here is roles/whoami/tasks/hostname/RedHat.yaml:

---
- name: Echo hostname
  debug:
    msg: "Hello World! I am {{ ansible_hostname }}."
roles/whoami/tasks/hostname/RedHat.yaml

AWX GUI – fetching the code

I have a Project called config-ansible (dev) which gets from my dev branch in the config/ansible repo:

Screenshot showing AWX GUI for the Project to import code from version control into AWX

I also have a Template called Server Setup:

Screenshot showing template for the Server Setup playbook, using an inventory and pulling code from the config-ansible (dev) project.

This has a survey (handled by when: echo_hostname | lower == 'yes' in my main.yaml file for the tasks) to allow environment variables to be passed down:

Survey edit page showing the Yes and No fields passed for the "Do you want to echo the host's hostname?" question.

We can now run the Server Setup playbook by running the template:

Pre-launch screen showing two survey questions for whether the user wants to echo the hostname and IP address of the box.

We see successful output from the job after launch:

Output from run of the Server Setup playbook in the AWX console showing the server's hostname and IP address (both redacted)

Even better… why not use a collection?