Bookmark this page

Creating Roles

Objectives

  • Create a role in a playbook's project directory and run it as part of one of the plays in the playbook.

The Role Creation Process

Creating roles in Ansible does not require any special development tools.

Creating and using a role is a three-step process:

  1. Create the role directory structure.

  2. Define the role content.

  3. Use the role in a playbook.

Creating the Role Directory Structure

Ansible looks for roles in a subdirectory called roles in the directory containing your Ansible Playbook. Each role has its own directory with a standardized directory structure. This structure allows you to store roles with the playbook and other supporting files.

For example, the following directory structure contains the files that define the motd role.

[user@host ~]$ tree roles/
roles/
└── motd
    ├── defaults
    │   └── main.yml
    ├── files
    ├── handlers
    ├── meta
    │   └── main.yml
    ├── README.md
    ├── tasks
    │   └── main.yml
    └── templates
        └── motd.j2

The README.md provides a basic human-readable description of the role, documentation, examples of how to use it, and any non-Ansible role requirements. The meta subdirectory contains a main.yml file that specifies information about the author, license, compatibility, and dependencies for the module.

The files subdirectory contains fixed content files and the templates subdirectory contains templates that the role can deploy.

The other subdirectories can contain main.yml files that define default variable values, handlers, tasks, role metadata, or variables, depending on their subdirectory.

If a subdirectory exists but is empty, such as handlers in this example, it is ignored. You can omit the subdirectory altogether if the role does not use a feature. This example omits the vars subdirectory.

Creating a Role Skeleton

You can create all the subdirectories and files needed for a new role by using standard Linux commands. Alternatively, command-line utilities exist to automate the process of new role creation.

The ansible-galaxy command-line tool (covered in more detail later in this course) is used to manage Ansible roles, including the creation of new roles. You can run ansible-galaxy init to create the directory structure for a new role. Specify the role's name as an argument to the command, which creates a subdirectory for the new role in the current working directory.

[user@host playbook-project]$ cd roles
[user@host roles]$ ansible-galaxy init my_new_role
- Role my_new_role was created successfully
[user@host roles]$ ls my_new_role/
defaults  files  handlers  meta  README.md  tasks  templates  tests  vars

Defining the Role Content

After creating the directory structure, you must write the content of the role. A good place to start is the ROLENAME/tasks/main.yml task file, the main list of tasks that the role runs.

The following tasks/main.yml file manages the /etc/motd file on managed hosts. It uses the template module to deploy the template named motd.j2 to the managed host. Because the template module is configured within a role task, instead of a playbook task, the motd.j2 template is retrieved from the role's templates subdirectory.

[user@host ~]$ cat roles/motd/tasks/main.yml
---
# tasks file for motd

- name: deliver motd file
  ansible.builtin.template:
    src: motd.j2
    dest: /etc/motd
    owner: root
    group: root
    mode: 0444

The following command displays the contents of the motd.j2 template of the motd role. It references Ansible facts and a system_owner variable.

[user@host ~]$ cat roles/motd/templates/motd.j2
This is the system {{ ansible_facts['hostname'] }}.

Today's date is: {{ ansible_facts['date_time']['date'] }}.

Only use this system with permission.
You can ask {{ system_owner }} for access.

The role defines a default value for the system_owner variable. The defaults/main.yml file in the role's directory structure is where this value is set.

The following defaults/main.yml file sets the system_owner variable to user@host.example.com. This email address is written in the /etc/motd file of managed hosts when this role is applied.

[user@host ~]$ cat roles/motd/defaults/main.yml
---
system_owner: user@host.example.com

Recommended Practices for Role Content Development

Roles allow you to break down playbooks into multiple files, resulting in reusable code. To maximize the effectiveness of newly developed roles, consider implementing the following recommended practices into your role development:

  • Maintain each role in its own version control repository. Ansible works well with Git-based repositories.

  • Use variables to configure roles so that you can reuse the role to perform similar tasks in similar circumstances.

  • Avoid storing sensitive information in a role, such as passwords or SSH keys. Configure role variables that are used to contain sensitive values when called in a play with default values that are not sensitive. Playbooks that use the role are responsible for defining sensitive variables through Ansible Vault variable files or other methods.

  • Use ansible-galaxy init command to start your role, then remove any unnecessary files and directories.

  • Create and maintain README.md and meta/main.yml files to document the role's purpose, author, and usage.

  • Keep your role focused on a specific purpose or function. Instead of making one role do many things, write more than one role.

  • Reuse roles often.

Resist creating new roles for edge configurations. If an existing role accomplishes most of the required configuration, refactor the existing role to integrate the new configuration scenario.

Use integration and regression testing techniques to ensure that the role provides the required new functionality and does not cause problems for existing playbooks.

Note

A longer unofficial list of good practices to follow when you write a role is available from https://redhat-cop.github.io/automation-good-practices/#_roles_good_practices_for_ansible.

Changing a Role's Behavior with Variables

A well-written role uses default variables to alter the role's behavior to match a related configuration scenario. Roles that use variables are more generic and reusable in a variety of contexts.

The value of any variable defined in a role's defaults directory is overwritten if that same variable is defined:

  • In an inventory file, either as a host variable or a group variable.

  • In a YAML file under the group_vars or host_vars directories of a playbook project.

  • As a variable nested in the vars keyword of a play.

  • As a variable when including the role in roles keyword of a play.

The following example shows how to use the motd role with a different value for the system_owner role variable. The value specified, someone@host.example.com, replaces the variable reference when the role is applied to a managed host.

[user@host ~]$ cat use-motd-role.yml
---
- name: use motd role playbook
  hosts: remote.example.com
  remote_user: devops
  become: true
  vars:
    system_owner: someone@host.example.com
  roles:
    - role: motd

When defined in this way, the system_owner variable replaces the value of the default variable of the same name. Any variable definitions nested within the vars keyword do not replace the value of the same variable if defined in a role's vars directory.

The following example also shows how to use the motd role with a different value for the system_owner role variable. The value specified, someone@host.example.com, replaces the variable reference regardless of being defined in the role's vars or defaults directory.

[user@host ~]$ cat use-motd-role.yml
---
- name: use motd role playbook
  hosts: remote.example.com
  remote_user: devops
  become: true
  roles:
    - role: motd
      system_owner: someone@host.example.com

Important

Variable precedence can be confusing when working with role variables in a play.

  • Most other variables override a role's default variables: inventory variables, play vars, inline role parameters, and so on.

  • Fewer variables can override variables defined in a role's vars directory. Facts, variables loaded with include_vars, registered variables, and role parameters can override these variables. Inventory variables and play vars cannot. This behavior is important because it helps keep your play from accidentally changing the internal functioning of the role.

  • Variables declared inline as role parameters have very high precedence; they can also override variables defined in a role's vars directory.

If a role parameter has the same name as a variable set in play vars, a role's vars, or an inventory or playbook variable, the role parameter overrides the other variable.

Defining Role Dependencies

Role dependencies allow a role to include other roles as dependencies.

For example, a role that defines a documentation server might depend upon another role that installs and configures a web server.

Dependencies are defined in the meta/main.yml file in the role directory hierarchy.

The following is a sample meta/main.yml file.

---
dependencies:
  - role: apache
    port: 8080
  - role: postgres
    dbname: serverlist
    admin_user: felix

Note

A meta/main.yml file might also have a top-level galaxy_info key that has a dictionary of other attributes that specify the author, purpose, license, and the versions of Ansible Core and operating systems that the role supports.

By default, if multiple roles have a dependency on a role, and that role is called by different roles in the play multiple times with the same attributes, then the role only runs the first time it appears. This behavior can be overridden by setting the allow_duplicates variable to yes in your role's meta/main.yml file.

Important

Limit your role's dependencies on other roles. Dependencies make it harder to maintain your role, especially if it has many complex dependencies.

Revision: rh294-9.0-c95c7de