Make it Reusable with Ansible Roles

As time goes on, your playbooks will grow to an unwieldy size, and you will need to refactor the code into separate logical groupings. At the same time, you’ll likely create additional Ansible repositories. Ansible roles are the solution.

Ansible roles simplify writing complex playbooks by breaking them into multiple files and providing a framework for developing a fully independent collection of variables, tasks, files, templates, and modules. The result is a reusable code base that can be shared among playbooks and repositories.

The directory structure will look like the one below:

Press + to interact
|-- roles/
| |-- chocolatey/
| | |-- tasks/
| | | |-- main.yml
| | |-- handlers/
| | | |-- main.yml
| | |-- files/
| | |-- templates/
| | |-- vars/
| | | |-- main.yml
| | |-- defaults/
| | | |-- main.yml
| | |-- meta

The highlighted lines represent the following:

  1. Name of the role.
  2. Ansible tasks and execution logic.
  3. Handlers for the role.
  4. Files required by the role, scripts, license key files, etc.
  5. Template files for Jinja2 templating.
  6. Non-overwritable variables localized to the role.
  7. Overwritable variables that provide defaults for the role.
  8. Metadata information about the role.

Roles expect files to be in specific directories. Each directory is optional. You can exclude any of the directories that are not in use.

Create an Ansible role

Scenario

Create an Ansible Role for Chocolatey.

Tasks

  1. Install Chocolatey.
  2. Install Chocolatey Core Extensions.

Files

Use the following files to install chocolatey on your remote Windows host.

Within the configure_iis_web_server.yml playbook, there is a task that installs a DotNet core. The task uses Chocolatey for the install. As a prerequisite, it also installs Chocolatey on the remote host.

Having the install dotnet task install Chocolatey works, but it doesn’t accurately define your configuration, and it isn’t explicit. Instead, it is implicit, which requires mental gymnastics when troubleshooting, and is something to be avoided.

Also, you’ll be installing Chocolatey across your entire environment. For that reason, it makes sense to modularize that configuration. Installing Chocolatey is an excellent opportunity for using a role.

Create the role directory structure

The Ansible role framework includes many directories to separate the different logic within a playbook.

Let’s start with the minimal directories you need. By default, Ansible looks within the roles/ directory relative to the playbook file for roles to use.

Review commands
We have already executed the commands and created the directories and files to save you from the hassle. You can review the commands to set up the structure below.

Create a directory named chocolatey within the roles directory.

Press + to interact
mkdir -p roles/chocolatey

Add a tasks and files directory within the chocolatey directory.

Press + to interact
mkdir roles/chocolatey/tasks
mkdir roles/chocolatey/files

Within the tasks directory, create a main.yml playbook.

Press + to interact
touch roles/chocolatey/tasks/main.yml

Move ChocolateyInstall.ps1 and chocolatey-core.extension.x.x.x.x.nupkg to the files directory.

Press + to interact
mv ~/Downloads/ChocolateyInstall.ps1 roles/chocolatey/files
mv ~/Downloads/chocolatey-core.extension.x.x.x.x.nupkg roles/chocolatey/files

The layout should look like the one below:

Press + to interact
|-- roles/
| |-- chocolatey/
| | |-- files/
| | | |-- ChocolateyInstall.ps1
| | | |-- chocolatey-core.extension.x.x.x.x.nupkg
| | |-- tasks/
| | | |-- main.yml

Default Directory
By default Ansible looks within the roles/ directory, relative to the playbook file, or the /etc/ansible/roles directory.

Write the role tasks

There are two tasks to install Chocolatey:

  • Install Chocolatey.
  • Install chocolatey.core.extension.

Each of these tasks must be automated with Ansible within the /tasks/main.yml playbook.

Create two tasks that register Ansible variables for the Windows $env:TEMP and $env:ProgramData environment variables. These temporary locations will be used for storing the downloads. ProgramData is the default Chocolatey location and will be used to copy the license.

Press + to interact
---
- name: register windows temp env var
win_shell: $env:TEMP
register: temp_out
changed_when: false
- name: register windows programdata env var
win_shell: $env:ProgramData
register: programData_out
changed_when: false

The tasks/main.yml does not require a hosts list or a tasks list. The hosts are provided when you call the role from another playbook, and a tasks list isn’t necessary because of the naming of the directory the file is in.

The win_shell module is an Ansible module that executes cmd and PowerShell commands. Using this module, you output the required environment variables and register them as Ansible variables using the register parameter.

A problem with win_shell is it will return a status of changed each time it runs.

Use changed_when to modify that behavior. Setting changed_when to false will ensure the win_shell module always returns OK, which is a more accurate representation of what it is doing. Since it’s just querying environment variables, it doesn’t change anything on the host.

Use the registered variables to set facts for file locations.

Press + to interact
- set_fact:
choco_location: "{{ programData_out.stdout | trim + '\\chocolatey' }}"
- set_fact:
temp_location: "{{ temp_out.stdout | trim }}"

Use set_fact to modify the Ansible variables by trimming the trailing \\ from the string and adding \\chocolatey to the choco_location variable.

Copy ChocolateyInstaller.ps1 to the remote Windows host and run the script.

Press + to interact
- name: copy ChocolateyInstall.ps1
win_copy:
src: ChocolateyInstall.ps1
dest: "{{ temp_location + '\\ChocolateyInstall.ps1' }}"
- name: install chocolatey
win_chocolatey:
name: chocolatey
state: present
source: "{{ temp_location + '\\ChocolateyInstall.ps1' }}"

Copy the chocolatey-core.extension NuGet package and install it with win_chocolatey.

Press + to interact
- name: copy chocolatey.extension
win_copy:
src: chocolatey-core.extension.1.3.5.1.nupkg
dest: "{{ temp_location + '\\chocolatey-core.extension.1.3.5.1.nupkg' }}"
tags: chocolateyExtension
- name: install chocolatey.extension
win_chocolatey:
name: chocolatey-core.extension
state: present
source: "{{ temp_location + '\\chocolatey-core.extension.1.3.5.1.nupkg' }}"
tags: chocolateyExtension

Using win_chocolatey to execute the nugget package provides an idempotent task. By default, win_chocolatey verifies if the package is already installed. If it is, it skips the install.

This is a good example of when it’s best not to use win_shell. Certainly, win_shell is a simple way to get things done. However, avoid relying on it when other modules are available.

Add roles to the site

Roles cannot be executed without a playbook calling them. By design, the tasks/main.yml does not include a hosts list. This is because the playbook calling the roles provides that information.

Update the site.yml to apply for the chocolatey role against the windows group. Define a new playbook with a hosts list and use the roles parameter to call the Chocolatey role.

---
- name: register windows temp env var
  win_shell: $env:TEMP
  register: temp_out
  changed_when: false

- name: register windows programdata env var
  win_shell: $env:ProgramData
  register: programData_out
  changed_when: false

- set_fact:
    choco_location: "{{ programData_out.stdout | trim + '\\chocolatey' }}"

- set_fact:
   temp_location: "{{ temp_out.stdout | trim  }}"

- name: copy ChocolateyInstall.ps1
  win_copy:
    src: ChocolateyInstall.ps1
    dest: "{{ temp_location + '\\ChocolateyInstall.ps1' }}"

- name: install chocolatey 
  win_chocolatey:
    name: chocolatey
    state: present
    source: "{{ temp_location + '\\ChocolateyInstall.ps1' }}"

- name: copy chocolatey.extension
  win_copy:
    src: chocolatey-core.extension.1.3.5.1.nupkg
    dest: "{{ temp_location + '\\chocolatey-core.extension.1.3.5.1.nupkg' }}"
  tags: chocolateyExtension
  
- name: install chocolatey.extension
  win_chocolatey:
    name: chocolatey-core.extension
    state: present
    source: "{{ temp_location  + '\\chocolatey-core.extension.1.3.5.1.nupkg' }}"
  tags: chocolateyExtension
Ansible roles

Order is everything in Ansible. The role must be added before the configure_iis_web_server.yml playbook runs. If it doesn’t, the DotNet task will attempt to install Chocolatey.

Update the <Password> with the password created using the ansible-vault command in the group_vars/linux.yml and group_vars/windows.yml files.

Click on the Run button, wait for the environment to set up, and execute the site.yml playbook to configure the web servers using the following command:

Press + to interact
ansible-playbook site.yml -i hosts --ask-vault-pass
#skip Chocolatey tasks
ansible-playbook site.yml -i hosts --skip-tags chocolateyExtension --ask-vault-pass

gather_facts: false
gather_facts: false turns off remote host fact gathering. When this is enabled it queries the remote system for specific details about the host and populates variables that Ansible can use.

In this lesson, we introduced roles, roles directory layout, and you created your first role, called chocolatey.

Get hands-on with 1300+ tech skills courses.