A lot of new networking modules were released as part of Ansible 2.1. The Cisco IOS, IOS XR, NXOS, Junos and Arista EOS platforms got three common modules, the platform_config, platform_command and platform_template. The command and template modules more or less explains themselves. The config modules have some more tricks to them and I’ve gotten a few questions about how they work. In this article I’m going to focus on the ios_config module and show how you can use it to configure Cisco IOS devices. Future version of Ansible will add more parameters, this article is for Ansible 2.1.
As mentioned above these modules were released in Ansible 2.1 so you must have at least that version installed. This article also assumes that you have some basic knowledge about Ansible.
To save some space it’s assumed that the username, password and host parameters are used in the examples below. I.e.:
- name: Configure device
ios_config:
host: "{{ inventory_hostname }}"
username: "{{ device_ssh_username }}"
password: "{{ device_ssh_password }}"
You might want to start by just reviewing the documentation.
ansible-doc ios_config
The parameters you will want to focus on are:
First off “commands” is an alias for “lines”, so you might see examples using both variants. Just remember that it’s the same thing.
According to the documentation (as it is now) the lines parameter is described like this:
The ordered set of commands that should be configured in the section. The commands must be the exact same commands as found in the device running-config. Be sure to note the configuration command syntax as some commands are automatically modified by the device config parser.
Basically it’s for defining the configuration lines you want in your device config. At first it might not be clear what is meant by the exact same commands. So let’s look at an example.
- name: Create an access-list
ios_config:
lines: access-list 180 permit tcp any any eq 80
The above task would work, and would add the line “access-list 180 permit tcp any any eq 80” to the configuration. However, it would not be idempotent, meaning that each time you would run the same task it would register as a change. This is because when you add this line to the configuration IOS will swap out port 80 and replace it with www. So the correct way to write the above task and make it idempotent would be like this:
- name: Create an access-list
ios_config:
lines: access-list 180 permit tcp any any eq www
The lines parameter accepts a single configuration line as above or a list like this:
- name: Configure service
ios_config:
lines:
- no service pad
- service timestamps debug uptime
- service timestamps log uptime
- service password-encryption
The after parameter is simply a list of configuration commands to run after the desired lines have been applied. An example where this could be useful is when configuring SNMPv3.
As part of the configuration you might want to add these lines:
snmp-server group SNMPv3 v3 priv
snmp-server user snmpv3 SNMPv3 v3 auth sha AUTHPW123 priv aes 128 Pr1vPW123
The problem is that the second command never shows up in the configuration, still it’s needed to create the user. If you want to configure this with Ansible in an idempotent way you couldn’t have the play look like this:
- name: Configure SNMPv3
ios_config:
lines:
- snmp-server group SNMPv3 v3 priv
- snmp-server user snmpv3 SNMPv3 v3 auth sha AUTHPW123 priv aes 128 Pr1vPW123
As it would register as a change each time you ran your playbook. As a workaround you could use the after parameter so that the snmp-server user was only added if the group was missing from the configuration.
- name: Configure SNMPv3
ios_config:
lines:
- snmp-server group SNMPv3 v3 priv
after:
- snmp-server user snmpv3 SNMPv3 v3 auth sha AUTHPW123 priv aes 128 Pr1vPW123
Some of the configuration in an IOS device is structured so that all the configuration under a specific item is indented. For example, the configuration related to an interface.
interface GigabitEthernet0/1
description Uplink
ip address 192.168.0.1 255.255.255.0
In the above case “interface GigabitEthernet0/1” would be the parent to description and ip address parameters. Using the ios_config module you could use a task looking like this:
- name: Configure Uplink
ios_config:
parents: "interface GigabitEthernet0/1"
lines:
- description Uplink
- ip address 192.168.0.1 255.255.255.0
You can use multiple parents for hierarchical configuration items. In IOS you typically see this if you are setting up policy-maps, for example if you are setting up something like zone based firewalls or dot1x your config might look like this:
policy-map type control subscriber POLICY_MAB
event session-started match-all
10 class always do-until-failure
10 authenticate using mab aaa authc-list ISE priority 20
Looking at setting up this in Ansible (with the xyz_config modules) it would look like this.
- name: Configure PM POLICY_MAB
ios_config:
parents:
- policy-map type control subscriber POLICY_MAB
- event session-started match-all
- 10 class always do-until-failure
lines:
- 10 authenticate using mab aaa authc-list ISE priority 20
At times you need to do something before the configuration is applied. An example might be if you want to remove an access-list before reapplying it.
- name: Configure TEST-ACL
ios_config:
parents: "ip access-list extended TEST-ACL"
lines:
- permit tcp any any eq smtp
- permit tcp any any eq www
before: "no ip access-list extended TEST-ACL"
If the access-list needed to be changed it would be removed first and then recreated.
The match parameter currently takes three parameters.
This is mostly self-explanatory, though a tricky part can be the exact statement as that one is context related. If you are using parents the configuration will be matched within that section. I.e. if your parent is “ip access-list extended TEST-ACL” the configuration the module matches your lines against will only be the ones under that access-list. However, if you don’t have any parents the match will be against the entire configuration.
Looking at the replace parameter it takes two parameters.
To illustrate the difference of the options let’s say wa have an access-list which looks like this:
ip access-list extended TEST-ACL
permit tcp any any eq www
Then we have an Ansible task looking like this.
- name: Configure TEST-ACL
ios_config:
parents: "ip access-list extended TEST-ACL"
lines:
- permit tcp any any eq smtp
- permit tcp any any eq www
before: "no ip access-list extended TEST-ACL"
match: exact
replace: line
Our end goal is to ensure that our access-list looks exactly as we desire. When we run this play we want an acl which allows smtp and http. However what we will end up with is an acl which looks like this:
ip access-list extended TEST-ACL
permit tcp any any eq smtp
The reason is that the line “permit tcp any any eq www” already exists in the access-list and since we use “replace: line” we only add the missing lines. Since we use the before parameter to remove the access-list we don’t reach the desired result. So what we need to do is to change the replace parameter to block. That way we will remove the acl and they recreate all of the desired entries.
So combining the match and replace parameters works well with indented configuration items where we have parents.
If we look at another example. We want to make sure that our snmp settings are exactly as we desire and if someone had configured extra traps we don’t want or other settings we just want to wipe them out with the “default snmp-server” command and only reapply the wanted config.
At first you might want to create a task looking like this:
- name: Configure SNMP
ios_config:
lines:
- snmp-server community cisco RO
- snmp-server location STH
- snmp-server contact NORTH
- snmp-server enable traps snmp linkdown linkup
- snmp-server host 172.29.52.11 version 2c public
before: "default snmp-server"
match: exact
replace: block
The problem here is that the match will be done against the entire configuration so it will always differ. A current workaround to this would be to use the config parameter to match against a pre filtered config file. Otherwise just keep in mind that the match is done differently depending on the context of your config lines.
In some scenarios it’s much easier to just use the different templates modules to configure your devices. But at times you want the configuration to be applied in a certain order and you might want to include commands which doesn’t show up in the configuration after you have reached the desired state.
One such scenario might be if you want to replace an access-list without breaking your own connection to the device.
- name: ACL - ACL-IN
ios_config:
parents: ["ip access-list extended ACL-IN"]
commands:
- permit tcp any any eq 22
- permit tcp any any eq www
- permit udp any any eq snmp
match: exact
replace: block
before:
- interface GigabitEthernet0/1
- no ip access-group ACL-IN in
- no ip access-list extended ACL-IN
after:
- interface GigabitEthernet0/1
- ip access-group ACL-IN in
notify: save configuration
This task would first check if the access-list looks exactly as we want it to do. If something has changed Ansible would remove the acl from the interface, delete the access-list and then recreate it and reapply it to the interface. In this example we also notify a handler to save the configuration at the end of the playbook, this will only happen if a change has actually been made.
You could also expand this and read the access-list from a file using lookup function or have the configuration be a condition of the output from ios_command or another similar module.
Now you should be familiar with how you can use the ios_config and other related modules. But remember, the _config modules is just one way you can use Ansible to configure your network devices. You should also get familiar with the _template modules and look at third party modules such as Napalm.