Networking with Ansible 102
Ansible modules, debugging & Network Fact Gathering
1. Cisco IOS Modules (basics) - Teaser version
1.1 ios_facts and using tags
Module: ios_facts - Collect facts from remote devices running Cisco IOS
In the above playbook using the iosfacts module we're using the ansibleconnection=local which will ship the module back to the ansible control machine we will be using. Inside the ios_facts module a SSH-connection to the network node will be executed.
We are fetching the host information from our inventory file under the group [cisco] along with the credentials stored in our inventory file under [cisco:vars].
tags
is a keyword that used under a task makes us be able to control when it should be run upon us calling the playbook. In this case by appending --tags facts_only
at the end of our CLI command it will skip the debug since this has not been tagged.
The first CLI example of running the playbook will only run the task that has been tagged with facts_only
, while the following will run both the ios_facts & the debug task.
ansible-playbook ios_example.yml -i ./../ansible-hosts --tags facts_only
ansible-playbook ios_example.yml -i ./../ansible-hosts --tags
1.2 ios_command and saving variables with the register keyword
The register keyword needs to be at the same level as the task itself.
16 - ios_command:
17 provider: "{{ creds }}"
18 commands: show ip int brief
19 register: output
20
21 - debug:
22 var: output
In the above example we use the register keyword to save the output of show ip int brief" to a variable named *output. We can then use this variable via the debug variable function. What we are returned is a dictionary consisting of three keys; output, stdout_lines and warnings.
To dig deeper within this datastructures, let us attempt to only make use of the value in stdoutlines key. We can accomplish this by ceasing to debug on the variable output itself and instead looking at the *stdout_lines-key* contained within output
21 - debug:
22 msg: "{{ output['stdout_lines'] }}"
The above change to the playbook would result in the following list:
Still, we can do better so let's get rid of the list wrapping in the output by digging deeper within the structure.
22 msg: "{{ output['stdout_lines'] }}"
22 msg: "{{ output.stdout_lines[0] }}"
By doing the following change we will only look at what is contained at index 0 in the list stdout_lines
, however as this is another list we can do an even better job:
22 msg: "{{ output.stdout_lines[0][1:6] }}"
This will give us the values from list index 1 up till list index 6, skipping the column headers and the last interface (Vlan1). We can also pick out explicit indexes by editing as per below:
22 msg: "{{ output.stdout_lines[0][6] }}"
Which in this case, would provide us only with information pertaining FastEthernet4.
2.0 Arista EOS Modules (basics) - Teaser version
Note that Arista supports both eAPI & SSH-connections, hence we need to setup the different providers under vars
and make use of the transport
key and and later, in the play send this information on how to establish a connection and which module we will be using.
Other than that, setting these modules up are basically the same as the IOS Module and the entry and the screenshot is mostly for future reference.
3.0 Conditionals (when) - Teaser version
The WHEN statement or how i learnt to control execution flows
The WHEN
statement is how ansible handles conditional control structures. In generic programming languages we do this with the help of if-then
, else if
, else
and case
statements. This helps our plays branch out towards different paths depending on certain conditions being met. Example use cases could be:
- If a certain variable is defined (
variable
is defined) - If a target node matches a defined OS version
- If a file exists
- If an earlier command sequence succeeded
Basically, statements that could be True
or False
such as:
- Is this switch running IOS version 15.2
- Is this ASA running ASA OS 9.5.2(14)
- Is interface X on switch Y down? (when down cmd: no shut)
While it is interesting to know if things are true or not the power of conditionals comes upon executing on this knowledge. If thirsty equals true, drink some water. If starving equals true, eat something.
This is not a primer on how conditional control structure works, go ahead a read throughthe Wikipedia article on Conditionals in computer programming and after that just go through and lab out some python examples just to get a better grasp on it.
But this is Ansible, and when working with Ansible we use WHEN
.
[Rows 1-9]
In the playbook we start with defining some variables we will be using in the examples that follows.
my_str:
whatever
version:
"Cisco IOS running version 15.2"
1 ---
2
3 - name: Ansible Conditionals WHEN
4 hosts: local
5 vars:
6 my_str: whatever
7 version: "Cisco IOS running version 15.2"
8 tasks:
9
[Rows 10-14] Hard-coded to False
10 - name: Hard-coded to False
11 debug:
12 msg: Hello World is false
13 when: False
14
This hard-coded task will never run since the debug message is set to only be displayed when False.
[Rows 15-19 Hard-coded to True]
15 - name: Hard-coded to True
16 debug:
17 msg: Hello world is true
18 when: True
19
This task has been hard-coded with when: true
and the debug message will therefore be printed out.
[Rows 20-24 Conditional Statement operators on a string
20 - name: WHEN my_str == whatever
21 debug:
22 msg: Since my_str was whatever (equals to)
23 when: my_str == 'whatever'
24
Now for something a bit more interesting, in the example below we are matching the variable we defined for our play to be whatever
and in this task we are using when:
variable operator
'string'.
There are 6 different operators to choose from:
< meaning less than
<= meaning less than or equal to
> meaning greater than
>= meaning greater than
== meaning equal
!= meaning not equal
So in this example we are simply asking: Is it true or false that the string variable my_str
has the same value as the string whatever
, and since that is the conditioal we ask for to be true in order to print out or debug message the task runs:
[Rows 25-29 Testing for a substring]
25 - name: substring testing version
26 debug:
27 msg: Testing to match for substring (substring-pattern)
28 when: "'15.2' in version"
29
In this case we are looking for the following substring 15.2
in the string variable version
which contains the following string Cisco IOS running version 15.2.
Since that is true in our case, the task is run.
[Rows 30-34 Testing for variable definitions]
30 - name: This variable is defined (definition-pattern)
31 debug:
32 msg: Pattern to decide IF the variable is defined at all
33 when: version is defined
34
This is a simple check to see if there actually is a variable named version
defined. Since this equals to true, our task runs.
[Rows 35-42 Continuing a play depending on a conditional outcome]
35 - name: Continue play task depending on conditional outcome
36 command: /bin/false
37 register: result
38 ignore_errors: True
39
40 - debug:
41 msg: "If you are reading this it means we successfully continued our play from the condition that if the play fails (We're invoking false) then it should print the debug messages you are reading."
42 when: result|failed
Ok, this example is a bit more interesting. In our task we invoke /bin/false
(we could also invoke /bin/true
in this case this basically sets the task result to failure.
In the conditional statement on line 42 we ask the debug message only to be displayed when variable result
contains an indication of failure, I am not entirely sure how that works but if you look at the main task in the output of the playbook, we can see that first it gives us the error message and since we have set our task to ignore errors the play continues. The default behaviour would be to abort the task, instead ansible is ...ignoring
it.
Since the variable result now indicates a failure the task continues with the rest of the task instead of aborting it and our debug message is printed out.
4.0 Loops (with_items) - Teaser version
No point in using Ansible if don't have scale in mind. The Loop construct helps us with this since we can iterate over lists and automating operations on many targets. We can install updates to systems, gather facts on a fleet of devices and we can even build monitoring systems by by performing certain actions (sendmail) if a result warrants it. Loops are both great and powerful!
As before, We set up some vars that we will use in our testing. We create a list of variables 2 different ways, in our first task we define the list in our task and in the second task we had defined a list called my_devices
in our playbook, we then call on this variable in the second task.
4.0 Playbook Setup
1 ---
2
3 - name: Loops with items
4 hosts: local
5 vars:
6 my_devices:
7 - 20.20.20.1
8 - 20.20.20.2
9 - 20.20.20.3
10 - 20.20.20.4
Task 1 setup: Test with manual list definition in task
11 tasks:
12 - name: Test with manual list definition in task
13 debug:
14 msg: "{{ item }}"
15 with_items:
16 - 10.10.10.1
17 - 10.10.10.2
18 - 10.10.10.3
19 - 10.10.10.4
This is the simplest task. The list is defined upon playbook execution for the task in question and ansible iterates over each entry and prints out the value for them.
Task 2 setup: Test with manual list definition in vars.my_devices
20 - name: Test with manual list definition in vars_my_devices
21 debug:
22 msg: "{{ item }}"
23 with_items: "{{ my_devices}}"
Here we make use of the list my_devices
we defined in the setup of our playbook, except for this the construct works the same.
Task 3 setup: Test with manual list definition in vars.my_devices + WHEN
24 - name: 'Test with manual list definition in vars_my_devices + WHEN'
25 debug:
26 msg: "{{ item }}"
27 with_items: "{{ my_devices}}"
28 when: "item == '20.20.20.3'"
This is the same as Task 2, except that we threw in a conditional WHEN for good measure. This tells Ansible to iterate over the list and print the debug message only when item == '20.20.20.3'. We also get a list of the items in the list that were skipped due to our conditional.
The coupling of the with_items
and the when
in the same task is an important concept.
5.0 Loops (with_dict) - Teaser version
Task 0: Playbook setup
This time for testing the Ansible Loop construct with dictionaries, there are some differences but those are mainly in how we handle the output. Below you have a setup and the variables we will be using to test this out.
1 ---
2 - name: Loops (with dict)
3 hosts: local
4 vars:
5 my_devices:
6 segot-nsw01:
7 ip_addr: 10.10.10.1
8 device_type: cisco_ios
9 segot-nsw02:
10 ip_addr: 10.10.10.2
11 device_type: cisco_ios
12 segot_nfw01:
13 ip_addr: 10.10.10.3
14 device_type: cisco_asa
15
16 tasks:
Test 1 setup: Basic with_dict test (for items in dict)
17 - name: Test1 with_dict (for items in dict)
18 debug:
19 msg: "{{ item }}"
20 with_dict: "{{ my_devices }}"
21
This is pretty straight-forward, for every iteration in our dictionary my_devices
we print out the contents of the debug message. Notice that this is a nested dictionary.
Test 2 setup: Basic granular with_dict test (for items in dict) + WHEN
22 - name: Test2 with_dict (for item.keys in dict)
23 debug:
24 msg: "{{ item.value.ip_addr }}"
25 with_dict: "{{ my_devices }}"
26 when: "'cisco_asa' in item.value.device_type"
27
Here we get a bit more granular and start looking deeper within our data structure via msg: "{{ item.value.ip_addr }}"
, this gets us rid of some excess information and show how we can only request the information we need.
We are also using a conditional to only print out the debug message when a device_type key has a value of cisco_asa
, it also just gives us the value of item.value.ip_addr
Test 3 setup: Another granular with_dict test (for items in dict) + WHEN
28 - name: Test with_dict (for item.values in dict)
29 debug:
30 msg: "{{ item.key }}"
31 with_dict: "{{ my_devices }}"
32 when: item.value.ip_addr != '10.10.10.3'
In this final test we are asking for the values items in the dictionary which does not have an IP-address of 10.10.10.3 with when: item.value.ip_addr != '10.10.10.3'
we also request that we only get the value of they key itself (in this case that is the hostname of the device) by stating it as msg: "{{ item.key }}".
And according to the below output we can see that have recieved two messages, one for segot-nsw01 and one for segot-nsw02.