Add a dhcp helper address where helpers exist


How do I add a DHCP helper address to an interface?

That should be easy, do you know which devices and interfaces?

It needs to be all devices and all interfaces that already have a helper address.

So you don't have a list maintained outside of the devices?

I could make a list from IPAM, but this needs to happen now, I can do the clean-up later.

Do you know the OS of the devices?

I could make a list...do I need to?

No. We'll figure it out.


That was basically how the conversation played out. The request was simple, add a DHCP helper where DHCP helpers exist, on every device.

This is part of the brownfield conversion process. The devices are still their own source of truth and are not fully managed by Ansible. There just isn't time every time to do it 100% right, but that doesn't mean we won't do it using Ansible. The steps down the road can be small as long as they are in the right direction.

Here's the final playbook that satisfied the request. Posted here, because it uses some techniques and Ansible capabilities that aren't always apparent to new Ansible developers.

These include:

  • A custom jinja filter to determine the OS of the network device.

  • A custom module that wraps ciscoconfparse to get specific sections out of a full running configuration.

  • A technique that keeps a running log of actual or proposed changes throughout the entire playbook, and produces a change log at the end. This helps in the ITSM change process.

  • A notify/handler that only saves the configuration once at the end of the playbook if a device was changed.

  • OS specific tasks and files, "do this if it is nxos, do this if it is ios"

  • loops, for every interface do this...

  • small, reusable roles that can be copied to a different playbook if needed, high reusability.

The site.yml file glues it all together, a unique role for each of the following:

  • Get the metadata, determine the OS
  • Go get the running configuration
  • Get all the interfaces that have a helper
  • Add a helper to each interface
  • Report on what did or needs to change
- name: run playbook
    hosts: all
    connection: local
    gather_facts: no
    roles:
      - metadata
      - retrieve_running
      - get_interfaces
      - add_helpers
      - report

The directory structure follows the Ansible best practises:

├── README.md
├── ansible.cfg
├── callback_plugins
├── filter_plugins
│   ├── determine_os.py
│   └── determine_os.pyc
├── group_vars
│   └── all
│       └── vars.yml
├── host_vars
├── inventory.txt
├── library
│   └── get_section.py
├── roles
│   ├── add_helpers
│   │   ├── meta
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── ios.yml
│   │       ├── ios_interface.yml
│   │       ├── main.yml
│   │       ├── nxos.yml
│   │       └── nxos_interface.yml
│   ├── get_interfaces
│   │   └── tasks
│   │       └── main.yml
│   ├── metadata
│   │   └── tasks
│   │       └── main.yml
│   ├── report
│   │   └── tasks
│   │       └── main.yml
│   ├── retrieve_running
│   │   ├── meta
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── asa.yml
│   │       ├── default.yml
│   │       ├── fwsm.yml
│   │       ├── ios.yml
│   │       ├── iosxe.yml
│   │       ├── main.yml
│   │       └── nxos.yml
│   └── save_config
│       └── handlers
│           └── main.yml
└── site.yml

Here's a few links to the interesting snippets:

The custom OS determination filter:

filter_plugins/determine_os.py

and it in use:

roles/metadata/tasks/main.yml

The new 'get_section' module that only returns interfaces that already have DHCP helpers:

library/get_section.py

and it in use:

roles/get_interfaces/tasks/main.yml

The change log defined for each device:

group_vars/all/vars.yml

the change log being added to:

roles/add_helpers/tasks/ios_interface.yml

and the report:

roles/report/tasks/main.yml

The "save config" tasks:

roles/save_config/handlers/main.yml

that has to be a dependency in other roles:

roles/add_helpers/meta/main.yml

and gets flagged when there is a change:

roles/add_helpers/tasks/ios_interface.yml

Use variables in the task names for better output:

roles/add_helpers/tasks/ios_interface.yml

This is what it looks like when it runs:

$ansible-playbook -i inventory.txt site.yml --check

PLAY [run playbook] ************************************************************

TASK [metadata : Run show version] *********************************************
ok: [router1.company.net]
ok: [router2.company.net]
ok: [router3.company.net]
ok: [router4.company.net]
ok: [router5.company.net]

TASK [metadata : Set fact for os and ssh_accessible] ***************************
ok: [router1.company.net]
ok: [router3.company.net]
ok: [router4.company.net]
ok: [router2.company.net]
ok: [router5.company.net]

TASK [retrieve_running : Include OS files for config retrieval] ****************
included: /working/roles/retrieve_running/tasks/ios.yml for router5.company.net, router3.company.net, router1.company.net, router4.company.net, router2.company.net

TASK [retrieve_running : Retrieve configuration for IOS] ***********************
ok: [router1.company.net]
ok: [router2.company.net]
ok: [router3.company.net]
ok: [router4.company.net]
ok: [router5.company.net]

TASK [retrieve_running : Set the running configuration as a fact] **************
ok: [router2.company.net]
ok: [router3.company.net]
ok: [router4.company.net]
ok: [router1.company.net]
ok: [router5.company.net]

TASK [get_interfaces : Get interfaces with helper_keyword] *********************
ok: [router2.company.net]
ok: [router1.company.net]
ok: [router5.company.net]
ok: [router3.company.net]
ok: [router4.company.net]

TASK [add_helpers : Include OS files for interface helper addition] ************
included: /working/roles/add_helpers/tasks/ios.yml for router4.company.net, router3.company.net, router2.company.net, router1.company.net, router5.company.net

TASK [add_helpers : Include files for interface helper addition on ios] ********
included: /working/roles/add_helpers/tasks/ios_interface.yml for router1.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router1.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router4.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router4.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router4.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router4.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router3.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router3.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router3.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router3.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router2.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router5.company.net
included: /working/roles/add_helpers/tasks/ios_interface.yml for router5.company.net

TASK [add_helpers : Add helper: router1.company.net GigabitEthernet0/1.200] ***
changed: [router1.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router1.company.net]

TASK [add_helpers : Add helper: router1.company.net GigabitEthernet0/1.800] ***
changed: [router1.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router1.company.net]

TASK [add_helpers : Add helper: router4.company.net GigabitEthernet0/1.240] ***
changed: [router4.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router4.company.net]

TASK [add_helpers : Add helper: router4.company.net GigabitEthernet0/1.250] ***
changed: [router4.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router4.company.net]

TASK [add_helpers : Add helper: router4.company.net GigabitEthernet0/1.406] ***
changed: [router4.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router4.company.net]

TASK [add_helpers : Add helper: router4.company.net GigabitEthernet0/1.840] ***
changed: [router4.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router4.company.net]

TASK [add_helpers : Add helper: router3.company.net GigabitEthernet0/1.240] ***
changed: [router3.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router3.company.net]

TASK [add_helpers : Add helper: router3.company.net GigabitEthernet0/1.250] ***
changed: [router3.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router3.company.net]

TASK [add_helpers : Add helper: router3.company.net GigabitEthernet0/1.406] ***
changed: [router3.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router3.company.net]

TASK [add_helpers : Add helper: router3.company.net GigabitEthernet0/1.840] ***
changed: [router3.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router3.company.net]

TASK [add_helpers : Add helper: router2.company.net Vlan10] *************
changed: [router2.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router2.company.net]

TASK [add_helpers : Add helper: router5.company.net GigabitEthernet0/1.200] ***
changed: [router5.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router5.company.net]

TASK [add_helpers : Add helper: router5.company.net GigabitEthernet0/1.800] ***
changed: [router5.company.net]

TASK [add_helpers : Append changes to log (ios)] *******************************
ok: [router5.company.net]

TASK [report : Show config changes for device] *********************************
ok: [router3.company.net] => {
    "msg": [
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.240",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.250",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.406",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.840",
        "ip helper-address 10.5.5.5"
    ]
}
ok: [router1.company.net] => {
    "msg": [
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.200",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.800",
        "ip helper-address 10.5.5.5"
    ]
}
ok: [router4.company.net] => {
    "msg": [
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.240",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.250",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.406",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.840",
        "ip helper-address 10.5.5.5"
    ]
}
ok: [router5.company.net] => {
    "msg": [
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.200",
        "ip helper-address 10.5.5.5",
        "*** ROLE: add_helpers ***",
        "interface GigabitEthernet0/1.800",
        "ip helper-address 10.5.5.5"
    ]
}
ok: [router2.company.net] => {
    "msg": [
        "*** ROLE: add_helpers ***",
        "interface Vlan10",
        "ip helper-address 10.5.5.5"
    ]
}

RUNNING HANDLER [save_config : ios_save_config] ********************************
ok: [router1.company.net]
 [WARNING]: only show commands are supported when using check mode, not executing `write memory`

ok: [router2.company.net]
ok: [router3.company.net]
ok: [router4.company.net]
ok: [router5.company.net]

PLAY RECAP *********************************************************************
router1.company.net : ok=15   changed=2    unreachable=0    failed=0
router4.company.net : ok=21   changed=4    unreachable=0    failed=0
router3.company.net : ok=21   changed=4    unreachable=0    failed=0
router5.company.net : ok=15   changed=2    unreachable=0    failed=0
router2.company.net : ok=12   changed=1    unreachable=0    failed=0

root@506b0a2ec1b2:/working#