July 28, 2017 · ansible network automation playbook eos ios eapi debug

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
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:

Basically, statements that could be True or False such as:

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 ---
 3 - name: Ansible Conditionals WHEN
 4   hosts: local
 5   vars:
 6     my_str: whatever
 7     version: "Cisco IOS running version 15.2"
 8   tasks:

[Rows 10-14] Hard-coded to False

10     - name: Hard-coded to False
11       debug:
12         msg: Hello World is false
13       when: False

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

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'

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"

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

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
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 ---
3 - name: Loops with items
4   hosts: local
5   vars:
6     my_devices:
7         -
8         -
9         -
10        - 

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         -
17         -
18         -
19         -

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  == ''"

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 == ''. 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:
8         device_type: cisco_ios
9       segot-nsw02:
10         ip_addr:
11         device_type: cisco_ios
12       segot_nfw01:
13         ip_addr:
14         device_type: cisco_asa
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 }}"

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"

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 != ''

In this final test we are asking for the values items in the dictionary which does not have an IP-address of with when: item.value.ip_addr != '' 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.