An Ansible role lets you load in:
to be used within your playbooks.
They can be as simple as loading in a few variables you always use between playbooks or projects, or as complicated as an entire workflow of tasks that execute based on device types from tons of conditional statements.
Ansible roles have a standard structure to them with directories named a specific way. At the bare minimum, you only need to include at least one of the directories, but you can leave out any of the ones you are not using. main.yml
is a special filename in Ansible, and it will look at that YAML file first before any other YAML file in each directory.
└── sample-role
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Ansible includes a command-line utility to create your own Ansible roles called ansible-galaxy
. This is very helpful because Ansible expects the roles to have a certain file structure. If you use the ansible-galaxy
command, there is no question that the directory structure aligns with what Ansible is expecting to see when it loads in the role. (Spelling errors are a common problem when working with Ansible expecting things a certain way.)
If Ansible is installed, execute the following command to create a sample-role
:
ansible-galaxy init sample-role
Ansible will then create the following directories and files in the current working directory:
$ tree .
.
└── sample-role
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
9 directories, 8 files
ansible-roles$
Even though you probably will not use all the directories listed above, they do not hurt anything by existing and not being used. The directories that see the most use within network automation are:
defaults
tasks
templates
vars
Ansible roles are a great way of bundling up commonly used tasks, variables, and even custom modules. A common practice in software engineering is to take code that is reusable across multiple situations and abstract it, such as in the Python class
feature. Roles are more limited in scope but follow a similar design thought process.
For example, if you are always executing the same set of pre/post checks in a network change using Ansible, that could be abstracted into an Ansible role you include before or after your change. You could include a series of tasks that send show
commands, save the outputs to a file sent through a formatting cleanup of a Jinja2 template, and put in directories that are organized per device. Regardless of which change you are doing, whether it is BGP or ACLs, you are going to have a series of commands sent to a device beforehand and afterward that you will want captured in output files. An Ansible role is a great example of that use case, where you have the exact commands set as variables that can be fed into the role at runtime.
There are a couple of different ways to use roles, though the classic way is to include them at the playbook play definition level:
---
- name: "PLAY 1: SHOW COMMANDS FROM ROLE"
hosts: routers
connection: network_cli
roles:
- sample-role
tasks:
- name: "Print sample-role show command output"
debug:
msg: "{{ show_command_output }}"
Working with the following inventory file:
[routers]
sandbox-iosxe-recomm-1.cisco.com ansible_network_os=ios ansible_user=developer ansible_password=C1sco12345
Assuming we merely update the sample-role/tasks/main.yml
with a single task:
---
# tasks file for sample-role
- name: run multiple commands on remote nodes
ios_command:
commands:
- show ip interface brief
- show ip route
register: show_command_output
Note that the tasks file in the role has no Ansible play definition, hosts, or anything but the list of tasks without the tasks
key. Before the tasks in the playbook begin, it will execute the tasks and load in the variables from the role.
Note in the output the line [sample-role : run multiple commands on remote nodes]
, which includes the task from the role.
Here is some sample output (truncated for length):
(ansible-demo) ansible-roles$ ansible-playbook -i inventory working-with-roles.yaml
PLAY [PLAY 1: SHOW COMMANDS FROM ROLE] *****************************************
TASK [sample-role : run multiple commands on remote nodes] *********************
ok: [sandbox-iosxe-recomm-1.cisco.com]
TASK [Print sample-role show command output] ***********************************
ok: [sandbox-iosxe-recomm-1.cisco.com] => {
"msg": [
[
"Interface IP-Address OK? Method Status Protocol",
"GigabitEthernet1 10.10.20.48 YES other up up ",
"GigabitEthernet2 10.255.255.1 YES other down down ",
"GigabitEthernet3 192.168.30.1 YES other down down ",
"Loopback1 2.2.2.2 YES manual up up ",
"Loopback2 unassigned YES other up up ",
"Loopback5 5.5.5.5 YES manual up up ",
"Loopback7 192.168.0.254 YES other up up ",
"Loopback21 unassigned YES unset up up ",
"Loopback50 1.1.1.1 YES other up up ",
...
],
[
"Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP",
" D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area ",
" N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2",
" E1 - OSPF external type 1, E2 - OSPF external type 2",
" i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2",
" ia - IS-IS inter area, * - candidate default, U - per-user static route",
" o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP",
" a - application route",
" + - replicated route, % - next hop override, p - overrides from PfR",
"",
"Gateway of last resort is 10.10.20.254 to network 0.0.0.0",
"",
"S* 0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1",
" 2.0.0.0/8 is variably subnetted, 4 subnets, 2 masks",
"C 2.2.2.0/24 is directly connected, Loopback1",
"L 2.2.2.2/32 is directly connected, Loopback1",
"C 2.22.2.0/24 is directly connected, Loopback222",
"L 2.22.2.22/32 is directly connected, Loopback222",
" 3.0.0.0/8 is variably subnetted, 2 subnets, 2 masks",
"C 3.32.3.0/24 is directly connected, Loopback3333",
"L 3.32.3.32/32 is directly connected, Loopback3333",
" 5.0.0.0/32 is subnetted, 2 subnets",
"C 5.5.5.5 is directly connected, Loopback5",
"C 5.255.255.5 is directly connected, Loopback2022",
" 9.0.0.0/32 is subnetted, 1 subnets",
....
]
]
}
PLAY RECAP *********************************************************************
sandbox-iosxe-recomm-1.cisco.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
(ansible-demo) ansible-roles$
There are other ways of using roles so that the tasks you import are executed before or after your tasks, but they are outside the scope of this introduction.
Congrats on finishing the lab!