Post

Pitfalls with Ansible and Cisco ios_config module

Ansible’s ios_config is a real lifesaver for cisco based configuration management. But here are some things which I encountered and want to share.

IP Access list

Here we’ve a simple task which creates an access list

1
2
3
4
5
6
7
8
9
10
11
- name: configure acl
  ios_config:
    lines:
      - 'deny tcp any 10.0.0.0 0.0.255.255'
      - 'permit tcp any any'
      - 'permit icmp any any'
    parents: 'ip access-list extended vlan90_in'
    before: 'no ip access-list extended vlan90_in'
    match: 'exact'
    provider: ""
    replace: 'line'

Executing the playbook reveals nothing special:

1
2
3
4
5
6
7
8
9
ansible-playbook -D switch playbooks/acl.yml

PLAY [acl] *********************************************************

TASK [ switch : configure acl] *********************************************
changed: [switch] => (item={'key': u'vlan90_in', 'value': {u'lines': [u'deny tcp any 10.0.0.0 0.0.255.255', u'permit tcp any any', u'permit icmp any any']}})

PLAY RECAP *********************************************************************
switch : ok=0   changed=1    unreachable=0    failed=0

Doing it again although looks more interesting. We changed nothing but ios_config module thinks different. That’s not what we want.

1
2
3
4
5
6
7
8
9
ansible-playbook -D switch playbooks/acl.yml

PLAY [acl] *********************************************************

TASK [ switch : configure acl] *********************************************
changed: [switch] => (item={'key': u'vlan90_in', 'value': {u'lines': [u'deny tcp any 10.0.0.0 0.0.255.255', u'permit tcp any any', u'permit icmp any any']}})

PLAY RECAP *********************************************************************
switch : ok=0   changed=1    unreachable=0    failed=0

Let’s see how the json output looks like

1
2
3
4
5
6
7
8
9
10
11
12
13
ansible-playbook -D switch playbooks/acl.yml -vvv
...
"item": {
    "key": "vlan90_in",
    "value": {
        "lines": [
            "deny tcp any 10.0.0.0 0.0.255.255",
            "permit tcp any any",
            "permit icmp any any"
        ]
    }
},
...

On the module page we can read following for the line option

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.

Comparing the ansible task and the running config reveals the truth

1
2
3
4
5
# running-config
ip access-list extended vlan90_in
 deny   tcp any 10.0.0.0 0.0.255.255
 permit tcp any any
 permit icmp any any

For the deny acl entry the cisco parser adds some whitespaces between deny and tcp and this is recognized by the ansible module. The lines need to match exactly with the running config as stated in the module text.

1
2
3
4
5
6
7
8
9
10
11
- name: configure acl
  ios_config:
    lines:
      - 'deny   tcp any 10.0.0.0 0.0.255.255'
      - 'permit tcp any any'
      - 'permit icmp any any'
    parents: 'ip access-list extended vlan90_in'
    before: 'no ip access-list extended vlan90_in'
    match: 'exact'
    provider: ""
    replace: 'line'

Rerunning the playbook gives us the result we want

1
2
3
4
5
6
7
8
9
ansible-playbook -D switch playbooks/acl.yml

PLAY [acl] *********************************************************

TASK [ switch : configure acl] *********************************************
ok: [switch] => (item={'key': u'vlan90_in', 'value': {u'lines': [u'deny tcp any 10.0.0.0 0.0.255.255', u'permit tcp any any', u'permit icmp any any']}})

PLAY RECAP *********************************************************************
switch : ok=1   changed=0    unreachable=0    failed=0

Switch interface

We configure an interface with a jinja template

1
2
3
4
5
- name: configure interface
  ios_config:
    src: 'interface.j2'
    provider: ""
    defaults: 'yes'
1
2
3
4
5
{# interface.j2 #}
interface TenGigabitEthernet1/1
description *** test ***
switchport mode trunk
no shutdown

If you would run this task/playbook the second time it will again give you the changed status. Checking the switch config shows that the interface section intends the options with one whitespace

1
2
3
4
# running-config
interface TenGigabitEthernet1/1
 description *** test ***
 switchport mode trunk

Maybe you noticed the ios_config option defaults in the task I used

This argument specifies whether or not to collect all defaults when getting the remote device running config. When enabled, the module will get the current config by issuing the command show running-config all.

This can be a very powerful option but gives us also more to think when defining more options at the template.

Adding the native vlan for the trunk to the template will again cripple the idempotency.

1
2
3
4
5
6
{# interface.j2 #}
interface TenGigabitEthernet1/1
 description *** test ***
 switchport mode trunk
 switchport trunk native vlan 100
 no shutdown

If you run show running-config all at the switch the switchport trunk native vlan option comes before switchport mode trunk.

You also need to consider the order to make it correct.


Tested with:

  • Ansible 2.2.1
  • Cisco Catalyst 4500X, IOS-XE 03.06.03
This post is licensed under CC BY 4.0 by the author.