Assigning IP Addresses to Devices from NetBox with Ansible

Interfacing Ansible, NetBox, and production equipment can sometimes feel like a daunting task.  Once you’ve successfully setup Ansible and have some basic interactions working( which this blog Getting Started with Network Automation: NetBox + Ansible from Eric Goebelbecker does a phenomenal job at discussing) you can look to start expanding into more complicated tasks or additional platforms.

In this blog, I’m going to walk through how we extend our Ansible usage into additional platforms.  We’re going to use Ansible to assign an IP address designated for a specific interface directly to the interface.  For the purposes of this lab I’ll be using virtual switches but these steps should work on physical switches as well.

To start, let’s define the platforms in use.  For this lab I’ll have five devices.  Cisco NX-OS, Cisco IOS, Arista vEOS, Juniper vQFX, and VyOS.  Each of these 5 devices have been configured in NetBox to match their virtual counterpart.

Five IP address assignments for devices stored in NetBox are shown via five screenshots.

With each of the five devices created, we can now begin leveraging Ansible to change the first interface on each device.  We’ll be setting up five playbooks, one for each specific device type, and then having Ansible leverage the identified management IP to change the interface we’re targeting.  For each of the five devices we’ll look at the playbook, the device configuration before, the Ansible execution, and the device configuration after.

Virtual NX-OS

Let’s start with our before config:

vNXOS# show run int eth2/1

interface Ethernet2/1
shutdown
no switchport

vNXOS#

And now we look at our corresponding Ansible playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ cat 01_cisco--nx-os.yml 
---
- name: Set NX-OS IP address
  hosts: device_types_vnxos
  connection: network_cli
  become: true

  vars:
    ansible_user: admin
    ansible_password: admin
    ansible_become: yes
    ansible_become_password: admin
    ansible_network_os: cisco.nxos.nxos
    target_interface: Ethernet2/1

  tasks:
    - name: Configure Layer3 Mode and Enable Port
      cisco.nxos.nxos_interfaces:
        config:
          - name: "{{ target_interface }}"
            mode: layer3
            enabled: true
      loop: "{{ interfaces }}"
      when: item.name == target_interface 

    - name: Configure IP address
      cisco.nxos.nxos_l3_interfaces:
        config:
          - name: "{{ target_interface }}"
            ipv4:
              - address: "{{ item.ip_addresses[0].address }}"
      loop: "{{ interfaces }}"
      when: item.name == target_interface 
(.venv) jvillarreal@dev-box:~/ansible/ip-blog$

We can see that in our “Configure Layer3 Mode and Enable Port” task that we’re targeting the interface named Ethernet2/1.  We loop through all of our interfaces until we find that one, by name, and then configure it to be in Layer 3 mode and be enabled.  Then under “Configure IP address” we take the first IP address out of NetBox for that interface and assign it to the same named interface.

Next, we execute the playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 01_cisco--nx-os.yml

PLAY [Set NX-OS IP address] **********************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************
ok: [IP Blog NXOS]

TASK [Configure Layer3 Mode and Enable Port] *****************************************************************************************
changed: [IP Blog NXOS] => (item={'id': 11591, 'url': 'https://192.168.86.92:443/api/dcim/interfaces/11591/', 'display': 'Ethernet2/1'
, 'device': {'id': 644, 'url': 'https://192.168.86.92:443/api/dcim/devices/644/', 'display': 'IP Blog NXOS', 'name': 'IP Blog NXOS'}, 
'vdcs': [], 'module': None, 'name': 'Ethernet2/1', 'label': '', 'type': {'value': 'virtual', 'label': 'Virtual'}, 'enabled': True, 
'parent': None, 'bridge': None, 'lag': None, 'mtu': None, 'mac_address': None, 'speed': None, 'duplex': None, 'wwn': None, 
'mgmt_only': False, 'description': '', 'mode': None, 'rf_role': None, 'rf_channel': None, 'poe_mode': None, 'poe_type': None, 
'rf_channel_frequency': None, 'rf_channel_width': None, 'tx_power': None, 'untagged_vlan': None, 'tagged_vlans': [], 
'mark_connected': False, 'cable': None, 'cable_end': '', 'wireless_link': None, 'link_peers': [], 'link_peers_type': None, 
'wireless_lans': [], 'vrf': None, 'l2vpn_termination': None, 'connected_endpoints': None, 'connected_endpoints_type': None, 
'connected_endpoints_reachable': None, 'tags': [], 'custom_fields': {}, 'created': '2023-06-26T19:18:44.487337Z', 
'last_updated': '2023-06-29T17:14:46.954713Z', 'count_ipaddresses': 1, 'count_fhrp_groups': 0, '_occupied': False, 
'ip_addresses': [{'id': 197, 'url': 'https://192.168.86.92:443/api/ipam/ip-addresses/197/', 'display': '10.10.10.1/24', 
'family': {'value': 4, 'label': 'IPv4'}, 'address': '10.10.10.1/24', 'vrf': None, 'tenant': None, 
'status': {'value': 'active', 'label': 'Active'}, 'role': None, 'nat_inside': None, 'nat_outside': [], 'dns_name': '', 
'description': '', 'comments': '', 'tags': [], 'custom_fields': {}, 'created': '2023-06-28T19:18:18.383547Z', 
'last_updated': '2023-06-28T19:18:18.383561Z'}]})

TASK [Configure IP address] **********************************************************************************************************
changed: [IP Blog NXOS] => (item={'id': 11591, 'url': 'https://192.168.86.92:443/api/dcim/interfaces/11591/', 'display': 'Ethernet2/1'
, 'device': {'id': 644, 'url': 'https://192.168.86.92:443/api/dcim/devices/644/', 'display': 'IP Blog NXOS', 'name': 'IP Blog NXOS'}, 
'vdcs': [], 'module': None, 'name': 'Ethernet2/1', 'label': '', 'type': {'value': 'virtual', 'label': 'Virtual'}, 'enabled': True, 
'parent': None, 'bridge': None, 'lag': None, 'mtu': None, 'mac_address': None, 'speed': None, 'duplex': None, 'wwn': None, 
'mgmt_only': False, 'description': '', 'mode': None, 'rf_role': None, 'rf_channel': None, 'poe_mode': None, 'poe_type': None, 
'rf_channel_frequency': None, 'rf_channel_width': None, 'tx_power': None, 'untagged_vlan': None, 'tagged_vlans': [], 
'mark_connected': False, 'cable': None, 'cable_end': '', 'wireless_link': None, 'link_peers': [], 'link_peers_type': None, 
'wireless_lans': [], 'vrf': None, 'l2vpn_termination': None, 'connected_endpoints': None, 'connected_endpoints_type': None, 
'connected_endpoints_reachable': None, 'tags': [], 'custom_fields': {}, 'created': '2023-06-26T19:18:44.487337Z', 
'last_updated': '2023-06-29T17:14:46.954713Z', 'count_ipaddresses': 1, 'count_fhrp_groups': 0, '_occupied': False, 
'ip_addresses': [{'id': 197, 'url': 'https://192.168.86.92:443/api/ipam/ip-addresses/197/', 'display': '10.10.10.1/24', 
'family': {'value': 4, 'label': 'IPv4'}, 'address': '10.10.10.1/24', 'vrf': None, 'tenant': None, 
'status': {'value': 'active', 'label': 'Active'}, 'role': None, 'nat_inside': None, 'nat_outside': [], 'dns_name': '', 
'description': '', 'comments': '', 'tags': [], 'custom_fields': {}, 'created': '2023-06-28T19:18:18.383547Z', 
'last_updated': '2023-06-28T19:18:18.383561Z'}]})

PLAY RECAP ***************************************************************************************************************************
IP Blog NXOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ 

We can see the playbook recap has identified two changes.  Let’s check on the device.

vNXOS# show run int eth2/1

interface Ethernet2/1
no switchport
ip address 10.10.10.1/24
no shutdown

vNXOS#

And we can see that our port is now a Layer 3 interface with the designated IP.  Going forward I’ll be truncating output of the Ansible playbooks.

Virtual IOS

Same as before.  We’ll start with our before config:

vIOS#show run int gi 0/0

interface GigabitEthernet0/0
no ip address
shutdown
duplex auto
speed auto
end

vIOS#

Then examine our playbook:

---
- name: Set IOS IP address
  hosts: device_types_vios
  connection: network_cli
  become: true

  vars:
    ansible_user: cisco
    ansible_password: cisco
    ansible_become: yes
    ansible_become_password: admin
    ansible_network_os: cisco.ios.ios
    target_interface: GigabitEthernet0/0

  tasks:
    - name: Configure Enable Port
      cisco.ios.ios_interfaces:
        config:
          - name: "{{ target_interface }}"
            enabled: true
      loop: "{{ interfaces }}"
      when: item.name == target_interface

    - name: Configure IP address
      cisco.ios.ios_l3_interfaces:
        config:
          - name: "{{ target_interface }}"
            ipv4:
              - address: "{{ item.ip_addresses[0].address }}"
      loop: "{{ interfaces }}"
      when: item.name == target_interface

Now we execute the playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 02_cisco--ios.yml 

PLAY [Set IOS IP address] ************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************
ok: [IP Blog vIOS]

TASK [Configure Enable Port] *********************************************************************************************************
changed: [IP Blog vIOS] => {{ Truncated Output }}

TASK [Configure IP address] **********************************************************************************************************
changed: [IP Blog vIOS] => {{ Truncated Output }}

PLAY RECAP ***************************************************************************************************************************
IP Blog vIOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$



And now the switch output:

vIOS#show run int gi 0/0

interface GigabitEthernet0/0
ip address 10.10.10.2 255.255.255.0
duplex auto
speed auto
end

vIOS#

Virtual EOS

Our very terse before config:

vEOS(config)#show run int e1
interface Ethernet1

And next, the playbook:

---
- name: Set vEOS IP address
  hosts: device_types_veos
  connection: network_cli
  become: true
  
  vars:
    ansible_user: admin
    ansible_password: admin
    ansible_become: yes
    ansible_become_password: admin
    ansible_network_os: arista.eos.eos
    target_interface: Ethernet1

  tasks:
    - name: Configure Layer3 Mode and Enable Port
      arista.eos.eos_interfaces:
        config:
          - name: "{{ target_interface }}"
            mode: layer3
            enabled: true
      loop: "{{ interfaces }}"
      when: item.name == target_interface

    - name: Configure IP address
      arista.eos.eos_l3_interfaces:
        config:
          - name: "{{ target_interface }}"
            ipv4:
              - address: "{{ item.ip_addresses[0].address }}"
      loop: "{{ interfaces }}"
      when: item.name == target_interface

Running the playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 03_arista--veos.yml

PLAY [Set vEOS IP address] ***********************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************
ok: [IP Blog vEOS]

TASK [Configure Layer3 Mode and Enable Port] *****************************************************************************************
changed: [IP Blog vEOS] => {{ Truncated Output }}

TASK [Configure IP address] **********************************************************************************************************
changed: [IP Blog vEOS] => {{ Truncated Output }}

PLAY RECAP ***************************************************************************************************************************
IP Blog vEOS : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$

And the updated config, again very terse:

vEOS#show run int e1
  interface Ethernet1
  no switchport
  ip address 10.10.10.3/24
vEOS#

VYoS

Our before config:

vyos@vyos# show interface ethernet eth0
 hw-id 50:00:00:03:00:00
[edit]

Our playbook:

---
- name: Set vVyOS IP address
  hosts: device_types_vvyos
  connection: network_cli
  become: true

  vars:
    ansible_user: vyos
    ansible_password: vyos
    ansible_become: yes
    ansible_become_password: vyos
    ansible_network_os: vyos.vyos.vyos
    target_interface: eth0

  tasks:
    - name: Configure Enable Port
      vyos.vyos.vyos_interfaces:
        config:
          - name: "{{ target_interface }}"
            enabled: true
      loop: "{{ interfaces }}"
      when: item.name == target_interface

    - name: Configure IP address
      vyos.vyos.vyos_l3_interfaces:
        config:
          - name: "{{ target_interface }}"
            ipv4:
              - address: "{{ item.ip_addresses[0].address }}"
      loop: "{{ interfaces }}"
      when: item.name == target_interface

Running our playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 04_vyow--vvyos.yml 

PLAY [Set vVyOS IP address] **********************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************
ok: [IP Blog vVyOS]

TASK [Configure Enable Port] *********************************************************************************************************
ok: [IP Blog vVyOS] => {{ Truncated Output }}

TASK [Configure IP address] **********************************************************************************************************
changed: [IP Blog vVyOS] => {{ Truncated Output }}

PLAY RECAP ***************************************************************************************************************************
IP Blog vVyOS : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Verifying the config

vyos@vyos# show interface ethernet eth0
address 10.10.10.4/24
hw-id 50:00:00:03:00:00
[edit]
vyos@vyos#

Virtual JUN-OS

And last, but certainly not least, our starting Juniper config:

root@vqfx-re# show interfaces xe-0/0/0 
unit 0 {
    family inet {
        dhcp;
    }
}
{master:0}[edit]
root@vqfx-re#

Our playbook:

---
- name: Set vQFX IP address
  hosts: device_types_vqfx
  connection: ansible.netcommon.netconf
  become: true

  vars:
    ansible_user: root
    ansible_password: Juniper
    ansible_network_os: junipernetworks.junos.junos
    target_interface: xe-0/0/0

  tasks:
    - name: Configure IP address
        junipernetworks.junos.junos_l3_interfaces:
          config:
            - name: "{{ target_interface }}"
              ipv4:
                - address: "{{ item.ip_addresses[0].address }}"
        state: replaced
      loop: "{{ interfaces }}"
      when: item.name == target_interface

Next up, executing our playbook:

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$ ansible-playbook 05_juniper--vqfx.yml 
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

PLAY [Set vQFX IP address] ***********************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************
ok: [IP Blog vQFX]

TASK [Configure IP address] **********************************************************************************************************
changed: [IP Blog vQFX] => {{ Truncated Output }}

PLAY RECAP ***************************************************************************************************************************
IP Blog vQFX : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

(.venv) jvillarreal@dev-box:~/ansible/ip-blog$

And finally, checking the results:

root@vqfx-re# show interfaces xe-0/0/0 
unit 0 {
    family inet {
        address 10.10.10.5/24;
    }
}

{master:0}[edit]
root@vqfx-re#

Wrapping It Up

Once you get the hang of how Ansible works, it becomes very easy to iterate and make specific playbooks for different platforms.  The majority of the time I spent troubleshooting through this document were really based around making sure I’m issuing the right commands for the right platforms.  For example, vNX-OS is a switch and vIOS is a router.  I ended up spending a good amount of time trying to understand why the “no switchport” commands were taking on vNX-OS and not on vIOS before it dawned on me.  Oh, that’s right.  Routers don’t have a switchport command because they aren’t switches!

In summary: Know your platform!

A note about “ok” in Ansible

As you look through my output, you’re going to find “ok” counts that exceed the number of changes we’re making.  Each of the playbooks runs a “Gathering Facts” task, which contributes to one of our ok’s.  Each Ansible module requires the usage of the “name” variable as well.  Even though we aren’t making a change to the name of the interfaces, that contributes to our ok count as well.  Subsequent runs of these playbooks would result in our ok count increasing by the number of successful changes.

Get more tutorials and resources by joining the NetBox community on Slack and signing up for our NetBox Community mailing list.

Ansible Modules Used:

  • NetBox – https://docs.ansible.com/ansible/latest/collections/netbox/netbox/index.html
  • Cisco NX-OS – https://docs.ansible.com/ansible/latest/collections/cisco/nxos/index.html
  • Cisco IOS – https://docs.ansible.com/ansible/latest/collections/cisco/ios/index.html
  • Arista EOS – https://docs.ansible.com/ansible/latest/collections/arista/eos/index.html
  • VYoS – https://docs.ansible.com/ansible/latest/collections/vyos/vyos/index.html
  • Juniper JUNOS – https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html
Share the Post:

Related Posts