Unleashing the Power of Nautobot Dynamic Inventory for Ansible
Unleashing the Power of Nautobot Dynamic Inventory for Ansible
I mentioned the Nautobot Ansible module briefly in the previous post, explaining how it can be utilized to manage data within Nautobot or even add new or update existing data. Additionally, it can serve as a dynamic inventory for Ansible. This is where the GraphQL part of Nautobot becomes extremely powerful.
Nautobot Dynamic Inventory
Install the ansible collection
1
ansible-galaxy collection install networktocode.nautobot
Create an inventory file using the nautobot.gql_inventory module. In this inventory file we need to include a few things
- plugin: This tells the Ansible dynamic inventory how to interact with Nautobot
- api_endpoint: Where to interact with Nautobot
- token: How to authorize the interaction
- query: This is how we request what is required from Nautobot, in our case we just need the device, device_id, role, and platform.
- group_by: This is used to group the hosts
1
2
3
4
5
6
7
8
9
10
---
plugin: networktocode.nautobot.gql_inventory
api_endpoint: https://nautobot.example.com
token: "API-TOKEN"
query:
devices:
role: name
id:
group_by:
- role.name
If you test this with ansible-inventory -v --list --yaml -i inventory/inventory.yml
you should see output like a typical static inventory. Two important thing to remember:
- you must set the primary ipv4 for each device in Nautobot if you need the IP address in the ansible_host key.
- After testing with just a primary ipv6 that the module only places an IPv4 address in the ansible_host key.
- You must set the token as an environmental variable named ‘NAUTOBOT_TOKEN’ if you want to leave your token out of the inventory file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
all:
children:
backbone_core_router:
hosts:
bbr01:
ansible_host: 192.168.18.101
ansible_network_os: cisco.ios.ios
id: 089377b0-5555-4ff4-8fc1-e690fb6595f2
name: bbr01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.101
role:
name: backbone_core_router
bbr02:
ansible_host: 192.168.18.102
ansible_network_os: cisco.ios.ios
id: 77b1110e-cb6a-4674-b131-016837ac15d5
name: bbr02
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.102
role:
name: backbone_core_router
bbr03:
ansible_host: 192.168.18.103
ansible_network_os: cisco.ios.ios
id: dedf2eab-01c4-48a0-949f-e9d0b0f39ad7
name: bbr03
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.103
role:
name: backbone_core_router
bbr04:
ansible_host: 192.168.18.104
ansible_network_os: cisco.ios.ios
id: f0012d1c-c42c-43b1-937b-975dce8c50fa
name: bbr04
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.104
role:
name: backbone_core_router
backbone_datacenter_router:
hosts:
bdr01-east:
ansible_host: 192.168.18.106
ansible_network_os: cisco.ios.ios
id: 3ef918b0-cfbc-4686-a6dc-18a6ea3e0127
name: bdr01-east
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.106
role:
name: backbone_datacenter_router
bdr01-west:
ansible_host: 192.168.18.105
ansible_network_os: cisco.ios.ios
id: 40e23137-8ec4-4c98-99c9-cb49cd728d2a
name: bdr01-west
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.105
role:
name: backbone_datacenter_router
datacenter_router:
hosts:
dc-east-rtr01:
ansible_host: 192.168.18.113
ansible_network_os: cisco.ios.ios
id: 8e2a4a9e-34ed-416c-8f5e-727d8e3977c8
name: dc-east-rtr01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.113
role:
name: datacenter_router
dc-west-rtr01:
ansible_host: 192.168.18.111
ansible_network_os: cisco.ios.ios
id: b3390317-8f50-4a9a-9070-1bb30e6db9a7
name: dc-west-rtr01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.111
role:
name: datacenter_router
datacenter_spine:
hosts:
dc-east-spine01:
ansible_host: 192.168.18.114
ansible_network_os: cisco.ios.ios
id: 2cfa8266-d66c-4133-a438-75b7bb5df722
name: dc-east-spine01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.114
role:
name: datacenter_spine
dc-west-spine01:
ansible_host: 192.168.18.112
ansible_network_os: cisco.ios.ios
id: 8776944a-53ee-4850-9bbf-73ade94f951c
name: dc-west-spine01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.112
role:
name: datacenter_spine
market_core_router:
hosts:
market-router01:
ansible_host: 192.168.18.109
ansible_network_os: cisco.ios.ios
id: 1c577231-b9b0-44bf-bfae-3625c553063e
name: market-router01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.109
role:
name: market_core_router
market-router02:
ansible_host: 192.168.18.110
ansible_network_os: cisco.ios.ios
id: 460f8f18-7199-4b52-b1f4-c1b64839b0c9
name: market-router02
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.110
role:
name: market_core_router
master:
hosts:
lab-k3s01:
ansible_host: 172.16.221.38
id: 890325f6-1383-4e0d-9838-ff8714bfe8f1
name: lab-k3s01
primary_ip4:
host: 172.16.221.38
role:
name: master
lab-k3s02:
ansible_host: 172.16.221.39
id: 8185e66b-34e9-4861-a039-c21cf867761d
name: lab-k3s02
primary_ip4:
host: 172.16.221.39
role:
name: master
lab-k3s03:
ansible_host: 172.16.221.40
id: 6d7905e1-ee2a-4488-b19e-6759b19bf9df
name: lab-k3s03
primary_ip4:
host: 172.16.221.40
role:
name: master
node:
hosts:
east-lab-k3s01:
ansible_host: 172.16.223.42
id: 630fa57b-deed-42a5-9e43-907c0cb3141c
name: east-lab-k3s01
primary_ip4:
host: 172.16.223.42
role:
name: node
west-lab-k3s01:
ansible_host: 172.16.222.41
id: 19264960-0049-4b37-8f42-920111c6d337
name: west-lab-k3s01
primary_ip4:
host: 172.16.222.41
role:
name: node
region_core_router:
hosts:
region-router01:
ansible_host: 192.168.18.107
ansible_network_os: cisco.ios.ios
id: cea0cc0b-1dc0-4d6f-975f-f0c6bd84d4c4
name: region-router01
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.107
role:
name: region_core_router
region-router02:
ansible_host: 192.168.18.108
ansible_network_os: cisco.ios.ios
id: c0f6a4cf-c882-47dd-af14-23ff1d67b95b
name: region-router02
platform:
napalm_driver: ios
primary_ip4:
host: 192.168.18.108
role:
name: region_core_router
Configuration Template Role
Now that we have the inventory sorted, lets start with the playbook, I will be using roles to organize the Ansible tasks, the first will build the configurations into a file and then push that file as a full configuration to the router.
1
2
3
4
5
6
7
8
---
- name: Build Router Configurations
hosts: bbr01
gather_facts: false
connection: network_cli
roles:
- role: generate_configs
The role folder structure should look like this
1
2
3
4
5
6
7
8
role
├── tasks
│ └── main.yml
├── templates
│ ├── cisco_ios.j2
│ └── ios
└── vars
└── main.yml
Configuration Template Role Task
In the tasks/main.yml we need to query Nautbot for the device information, make sure the router_configs directory exists, and if not create it, build the router config and store it the router_configs folder.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
- name: Get data from Nautobot
networktocode.nautobot.query_graphql:
url: ""
token: ""
query: ""
register: "nb_device"
delegate_to: localhost
- name: Ensure router_configs directory exists
file:
path: "./router_configs"
state: directory
delegate_to: localhost
- name: Build Cisco Configurations
template:
src: cisco_ios.j2
dest: "./router_configs/.cfg"
delegate_to: localhost
Configuration Template Role Vars
In the vars/main.yml I have stored the Nautobot query as a string. I use this to get absolutely everything I might ever need, because its fast so you might as well just get it all while I am there.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
query_string: |
{
device(id:"") {
config_context
name
vrf_assignments {
name
}
primary_ip4 {
address
}
primary_ip6 {
address
}
role {
name
}
platform {
name
manufacturer {
name
}
network_driver
}
location {
name
vlans {
id
name
vid
}
vlan_groups {
id
}
}
interfaces {
description
mac_address
enabled
vrf {
name
}
name
mode
cf_ospf_area
cf_ospf_cost
cf_ospf_network
cf_ospf_priority
ip_addresses {
address
}
connected_circuit_termination {
circuit {
cid
commit_rate
provider {
name
}
}
}
tagged_vlans {
vid
}
untagged_vlan {
vid
}
cable {
termination_a_type
status {
name
}
color
}
tagged_vlans {
locations {
name
}
id
}
tags {
id
}
}
}
}
Configuration Template Role Templates
Next we need to start building the Jinja templates that will be used to generate the router configurations. I organize these templates based on the device platform, this way it keeps everything organized and you keep more than one device template here.
In templates/cisco_ios.j2 I check the platform name and the role of the device so that I can make specific templates for each. I re-use what I can, but often I find it easier to separate out the templates so that they do not become to large to manage and understand.
Each platform template could be placed inside the ios folder in platform_templates. At this time I only have a single platform, but this can easily grow as needed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#jinja2: lstrip_blocks: "True", trim_blocks: "True"
{% set device = nb_device["data"]["device"] %}
{% if device['platform']['name'] == 'Cisco IOS' %}
{% if device["role"]["name"] == "backbone_core_router" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "backbone_datacenter_router" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "region_core_router" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "market_core_router" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "datacenter_router" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "datacenter_spine" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% elif device["role"]["name"] == "datacenter_switch" %}
{% include "./ios/platform_templates/ios_router.j2"%}
{% endif %}
{% endif %}
In the platform_templates/ios_router.j2, this will look similar to what you would expect to see on a cisco router after performing a ‘'’show run’’’ command. I replace the sections of the configuration with additional templates as needed, again to help keep the template from growing to large and readable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
version 15.4
service timestamps debug datetime msec
service timestamps log datetime msec
no service password-encryption
!
!
{% include './ios/hostname.j2' %}
!
boot-start-marker
boot-end-marker
!
aqm-register-fnf
!
{% include './ios/dns.j2' %}
!
{% if device['config_context']['ntp'] is defined %}
{% include './ios/ntp.j2' %}
{% endif %}
!
{% if device['config_context']['snmp'] is defined %}
{% include './ios/snmp.j2' %}
{% endif %}
{% include './ios/aaa.j2' %}
!
mmi polling-interval 60
no mmi auto-configure
no mmi pvc
mmi snmp-timeout 180
!
{% if device['vrf_assignments'] is not none %}
{% for vrf in device['vrf_assignments'] %}
vrf definition {{ vrf['name'] }}
!
address-family ipv4
exit-address-family
!
{% endfor %}
!
!
{% endif %}
{% include './ios/local_user.j2' %}
!
redundancy
!
!
ip ssh source-interface Ethernet0/3
ip ssh version 2
ip scp server enable
!
!
!
!
!
!
!
!
!
!
ipv6 unicast-routing
!
!
!
!
{% include './ios/interfaces.j2' %}
!
ip forward-protocol nd
!
!
no ip http server
no ip http secure-server
!
!
!
!
control-plane
!
!
!
!
!
!
!
!
{% if device['config_context']["routes"] is defined %}
{% if device['config_context']["routes"]["static"] is defined %}
{% for static in device['config_context']["routes"]["static"] %}
{{ static }}
{% endfor %}
{% endif %}
{% endif %}
!
!
{% if device['config_context']['prefix_lists'] is defined %}
{% include './ios/prefix_list.j2'%}
{% endif %}
!
{% if device['config_context']['route_maps'] is defined %}
{% include './ios/route_map.j2'%}
{% endif %}
!
{% if device['config_context']["bgp"] is defined %}
{% include './ios/bgp.j2' %}
{% endif %}
!
{% if device['config_context']["ospf"] is defined %}
{% include './ios/ospf.j2' %}
{% endif %}
!
{% include './ios/services.j2' %}
!
end
Some of these templates are short while others are a little more complicated as you will see. From the top the first template is ios/hostname.j2
1
2
3
4
hostname {{ device['name'].split('.')[0] }}
ios/dns.j2 - some of these for now just have simple text as a placeholder, but this could easily be stored in Nauotbot as a config Context.
ip domain-name example.com
ios/ntp.j2
1
2
3
4
5
6
7
8
9
10
{% for server in device['config_context']['ntp'] %}
{% if server['prefer'] %}
ntp server {{ server['ip'] }} prefer
{% else %}
ntp server {{ server['ip'] }}
{% endif %}
{% endfor %}
ios/snmp.j2
1
2
3
4
5
6
7
8
9
10
{% for community in device['config_context']["snmp"]["community"] %}
snmp-server community {{ community["name"] }} {{ community["role"] }}
{% endfor %}
snmp-server location {{ config_context["snmp"]["location"] }}
snmp-server contact {{ config_context["snmp"]["contact"] }}
{% for host in device['config_context']["snmp"]["host"] %}
snmp-server host {{ host["ip"] }} version {{ host["version"] }} {{ host["community"] }}
{% endfor %}
ios/aaa.j2
1
2
3
aaa authorization exec default local
!
no aaa root
ios/local_user.j2
1
2
username cisco privilege 15 password 0 cisco
ios/interfaces.j2 - Here I use the same method to separate out the interface to keep this from becoming to big and hard to read.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{% for interface in device['interfaces'] %}
interface {{ interface["name"] }}
{% if interface["description"] is defined and interface["description"] != "" %}
description {{ interface["description"] }}
{% endif %}
{% if 'lan' in interface["name"] %}
{% include "./ios/_svi.j2" %}
{% elif 'thernet' in interface["name"] %}
{% include "./ios/_physical.j2" %}
{% elif 'Loop' in interface["name"] %}
{% include "./ios/_loopback.j2" %}
{% elif 'anagement' in interface["name"] %}
{% include "./ios/_mgmt.j2" %}
{% endif %}
{% endfor %}
ios/_svi.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{% if interface["ip_addresses"] | length > 0 %}
{% for addr in interface["ip_addresses"] %}
{% if addr["address"] is defined and '.' in addr["address"] %}
ip address {{ addr["address"] }}
{% elif addr["address"] is defined and ':' in addr["address"] %}
ipv6 address {{ addr["address"] }}
{% endif %}
{% endfor %}
{% else %}
no ip address
{% endif %}
{% if interface["enabled"] == true %}
no shutdown
{% endif %}
ios/_physical.j2 - Here you an see that I can pull ACLs type information from the config context of the device, and then I am checking the device interface data for specifics on how to configure each interface.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{% if device['config_context']["acl"] is defined %}
{% if device['config_context']["acl"]["interfaces"] is defined %}
{% if device['config_context']["acl"]["interfaces"][interface["name"]] is defined %}
ip access-group {{ device['config_context']["acl"]["interfaces"][interface["name"]]["acl"] }} {{ device['config_context']["acl"]["interfaces"][interface["name"]]["direction"] }}
{% endif %}
{% endif %}
{% endif %}
{% if interface["mode"] == 'ACCESS' %}
switchport mode access
switchport access vlan {{ interface["untagged_vlan"]['vid'] }}
no shutdown
{% elif interface["mode"] == 'TAGGED_ALL' %}
switchport mode trunk
no shutdown
{% elif interface["ip_addresses"] | length > 0 %}
{% if interface['vrf']['name'] is defined %}
vrf forwarding {{ interface['vrf']['name']}}
{% endif %}
no shutdown
{% if interface["mac_address"] != none %}
mac-address {{ interface["mac_address"] }}
{% endif %}
{% if interface["ip_addresses"] | length > 0 %}
{% for addr in interface["ip_addresses"] %}
{% if addr["address"] is defined and '.' in addr["address"]%}
ip address {{ addr["address"] | ipaddr('address') }} {{ addr["address"] | ipaddr('netmask') }}
{% if interface['cf_ospf_area'] is not none %}
ip ospf {{ device['config_context']['ospf']['process_id'] }} area {{ interface['cf_ospf_area'] }}
{% if interface['cf_ospf_cost'] is not none %}
ip ospf cost {{ interface['cf_ospf_cost'] }}
{% endif %}
{% if interface['cf_ospf_network'] is not none %}
ip ospf network {{ interface['cf_ospf_network'] }}
{% endif %}
{% endif %}
{% elif addr["address"] is defined and ':' in addr["address"]%}
ipv6 address {{ addr["address"] }}
{% if interface['cf_ospf_area'] is not none %}
ospfv3 {{ device['config_context']['ospf']['process_id'] }} ipv6 area {{ interface['cf_ospf_area'] }}
{% if interface['cf_ospf_cost'] is not none %}
ospfv3 cost {{ interface['cf_ospf_cost'] }}
{% endif %}
{% if interface['cf_ospf_network'] is not none %}
ospfv3 network {{ interface['cf_ospf_network'] }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if interface["enabled"] == false %}
shutdown
{% endif %}
ios/_mgmt.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% if interface["description"] | length > 1 %}
description {{ interface["description"] }}
{% endif %}
vrf MGMT
{% if interface["ip_addresses"] | length > 0 %}
{% for addr in interface["ip_addresses"] %}
{% if addr["address"] is defined %}
ip address {{ addr["address"] | ipaddr('address') }} {{ addr["address"] | ipaddr('netmask') }}
{% endif %}
{% endfor %}
{% else %}
no ip address
{% endif %}
{% if interface["enabled"] == false %}
shutdown
{% endif %}
ios/_loopback.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% if interface["ip_addresses"] | length > 0 %}
{% for addr in interface["ip_addresses"] %}
{% if addr["address"] is defined and '.' in addr["address"]%}
ip address {{ addr["address"] | ipaddr('address') }} {{ addr["address"] | ipaddr('netmask') }}
{% if interface['cf_ospf_area'] is not none %}
ip ospf {{ device['config_context']['ospf']['process_id'] }} area {{ interface['cf_ospf_area'] }}
{% endif %}
{% elif addr["address"] is defined and ':' in addr["address"]%}
ipv6 address {{ addr["address"] }}
{% if interface['cf_ospf_area'] is not none %}
ospfv3 {{ device['config_context']['ospf']['process_id'] }} ipv6 area {{ interface['cf_ospf_area'] }}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
no ip address
{% endif %}
{% if interface["enabled"] == false %}
no shutdown
{% endif %}
ios/prefix_list.j2
1
2
3
4
5
6
7
{% for prefix in device['config_context']['prefix_lists'] %}
{% for ip in prefix['prefixes'] %}
ip prefix-list {{ prefix['name'] }} seq {{ loop.index0 * 10 + 10 }} {{ ip['action']}} {{ ip['ip']}}
{% endfor %}
{% endfor %}
ios/route_map.j2
1
2
3
4
5
6
7
8
9
10
{% for map in device['config_context']['route_maps'] %}
{% for match in map['match'] %}
route-map {{ map['name'] }} {{map['action'] }} 10
{% if match['prefix_list'] is defined %}
match ip address prefix-list {{ match['prefix_list'] }}
{% endif %}
{% endfor %}
{% endfor %}
ios/bgp.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
router bgp {{ device['config_context']["bgp"]["asn"] }}
{% for interface in device["interfaces"] %}
{% if 'Loop' in interface["name"] %}
{% for addr in interface.ip_addresses %}
{% if addr.address is defined and '.' in addr.address %}
{% set rid = addr.address | ipaddr('address') %}
bgp router-id {{ rid }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% for group in device['config_context']["bgp"]['peer_groups'] %}
neighbor {{ group['group'] }}-ipv4 peer-group
neighbor {{ group['group'] }}-ipv4 remote-as {{ group['remote_as'] }}
neighbor {{ group['group'] }}-ipv4 update-source {{ group['update_source'] }}
neighbor {{ group['group'] }}-ipv4 next-hop-self
{% endfor %}
{% for group in device['config_context']["bgp"]['peer_groups'] %}
neighbor {{ group['group'] }}-ipv6 peer-group
neighbor {{ group['group'] }}-ipv6 remote-as {{ group['remote_as'] }}
neighbor {{ group['group'] }}-ipv6 update-source {{ group['update_source'] }}
{% endfor %}
{% for neighbor in device['config_context']["bgp"]["neighbors"] %}
{% if neighbor["peer_group"] is defined and '.' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} peer-group {{ neighbor["peer_group"] }}-ipv4
{% elif neighbor["peer_group"] is defined and ':' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} peer-group {{ neighbor["peer_group"] }}-ipv6
{% elif neighbor["peer_group"] is not defined and '.' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} remote-as {{ neighbor["remote_as"] }}
{% endif %}
{% endfor %}
address-family ipv4 unicast
{% for neighbor in device['config_context']["bgp"]["neighbors"] %}
{% if neighbor["peer_group"] is defined and '.' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} activate
{% elif neighbor["peer_group"] is defined and ':' in neighbor["peer"] %}
no neighbor {{ neighbor["peer"] }} activate
{% elif neighbor["peer_group"] is not defined %}
neighbor {{ neighbor["peer"] }} activate
{% endif %}
{% endfor %}
{% if device['config_context']["bgp"]["redistribute"] is defined and device['config_context']["bgp"]["redistribute"] | length > 0 %}
{% for type in device['config_context']["bgp"]["redistribute"] if type['route_map'] is defined %}
redistribute {{ type["type"] }} route-map {{ type['route_map'] }}
{% else %}
redistribute {{ type["type"] }}
{% endfor %}
{% endif %}
address-family ipv6 unicast
{% for group in device['config_context']["bgp"]['peer_groups'] %}
neighbor {{ group['group'] }}-ipv6 next-hop-self
{% endfor %}
{% for neighbor in device['config_context']["bgp"]["neighbors"] %}
{% if neighbor["peer_group"] is defined and ':' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} activate
{% elif neighbor["peer_group"] is not defined and ':' in neighbor["peer"] %}
neighbor {{ neighbor["peer"] }} activate
{% endif %}
{% endfor %}
{% if device['config_context']["bgp"]["redistribute"] is defined and device['config_context']["bgp"]["redistribute"] | length > 0 %}
{% for type in device['config_context']["bgp"]["redistribute"] if type['route_map'] is defined %}
redistribute {{ type["type"] }} route-map {{ type['route_map'] }}
{% else %}
redistribute {{ type["type"] }}
{% endfor %}
{% endif %}
ios/ospf.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
router ospf {{ device['config_context']["ospf"]['process_id'] }}
{% for interface in device["interfaces"] %}
{% if 'Loop' in interface["name"] %}
{% for addr in interface.ip_addresses %}
{% if addr.address is defined and '.' in addr.address %}
{% set rid = addr.address | ipaddr('address') %}
router-id {{ rid }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% for passive_interface in device['config_context']["ospf"]['passive_interfaces'] %}
passive-interface {{ passive_interface}}
{% endfor %}
max-lsa {{ device['config_context']["ospf"]['max_lsa'] }}
!
{% if device['config_context']['ospfv3'] is defined %}
router ospfv3 {{ device['config_context']["ospf"]['process_id'] }}
{% for interface in device["interfaces"] %}
{% if 'Loop' in interface["name"] %}
{% for addr in interface.ip_addresses %}
{% if addr.address is defined and '.' in addr.address %}
{% set rid = addr.address | ipaddr('address') %}
router-id {{ rid }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
!
{% for address_family in device['config_context']['ospfv3']['address_families'] %}
address-family {{ address_family}}
{% endfor %}
{% endif %}
!
ios/services.j2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
line con 0
logging synchronous
line aux 0
line vty 0 4
privilege level 15
login local
transport preferred ssh
transport input all
transport output telnet ssh
!
!
Building the router Configuration results
After all of these files have been created we can now run the playbook and create the full configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ansible-playbook pb.build_router_configs.yml --ask-vault-pass
Vault password:
PLAY [Build Router Configurations] *******************************************************************************************************************************************************************************************************************
TASK [generate_configs : Get data from Nautobot] *****************************************************************************************************************************************************************************************************
ok: [bbr01 -> localhost]
TASK [generate_configs : Ensure router_configs directory exists] *************************************************************************************************************************************************************************************
ok: [bbr01 -> localhost]
TASK [generate_configs : Build Cisco Configurations] *************************************************************************************************************************************************************************************************
[DEPRECATION WARNING]: Use 'ansible.utils.ipaddr' module instead. This feature will be removed from ansible.netcommon in a release after 2024-01-01. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
changed: [bbr01 -> localhost]
PLAY RECAP *******************************************************************************************************************************************************************************************************************************************
bbr01 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
And the final product in the router_configs folder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
version 15.4
service timestamps debug datetime msec
service timestamps log datetime msec
no service password-encryption
!
!
hostname bbr01
!
boot-start-marker
boot-end-marker
!
aqm-register-fnf
!
ip domain-name example.com
!
!
no aaa new-model
!
mmi polling-interval 60
no mmi auto-configure
no mmi pvc
mmi snmp-timeout 180
!
vrf definition MGMT
!
address-family ipv4
exit-address-family
!
!
!
username cisco privilege 15 password 0 cisco
!
redundancy
!
!
ip ssh source-interface Ethernet0/3
ip ssh version 2
ip scp server enable
!
!
!
!
!
!
!
!
!
!
ipv6 unicast-routing
!
!
!
!
interface Ethernet0/0
no shutdown
ip address 172.16.100.0 255.255.255.254
ip ospf 1 area 0
ip ospf cost 100
ip ospf network point-to-point
ipv6 address 2601:100:c800:100::/127
ospfv3 1 ipv6 area 0
ospfv3 cost 100
ospfv3 network point-to-point
interface Ethernet0/1
no shutdown
ip address 172.16.100.2 255.255.255.254
ip ospf 1 area 0
ip ospf cost 100
ip ospf network point-to-point
ipv6 address 2601:100:c800:100::2/127
ospfv3 1 ipv6 area 0
ospfv3 cost 100
ospfv3 network point-to-point
interface Ethernet0/2
no shutdown
ip address 172.16.100.4 255.255.255.254
ip ospf 1 area 0
ip ospf cost 100
ip ospf network point-to-point
ipv6 address 2601:100:c800:100::4/127
ospfv3 1 ipv6 area 0
ospfv3 cost 100
ospfv3 network point-to-point
interface Ethernet0/3
vrf forwarding MGMT
no shutdown
ip address 192.168.18.101 255.255.255.0
interface Loopback0
ip address 172.16.100.128 255.255.255.255
ip ospf 1 area 0
ipv6 address 2601:100:c800:100::128/128
ospfv3 1 ipv6 area 0
!
ip forward-protocol nd
!
!
no ip http server
no ip http secure-server
!
!
!
!
control-plane
!
!
!
!
!
!
!
!
!
!
!
!
router bgp 100
bgp router-id 172.16.100.128
neighbor backbone-ipv4 peer-group
neighbor backbone-ipv4 remote-as 100
neighbor backbone-ipv4 update-source Loopback0
neighbor backbone-ipv4 next-hop-self
neighbor backbone-ipv6 peer-group
neighbor backbone-ipv6 remote-as 100
neighbor backbone-ipv6 update-source Loopback0
neighbor 172.16.100.129 peer-group backbone-ipv4
neighbor 172.16.100.130 peer-group backbone-ipv4
neighbor 172.16.100.131 peer-group backbone-ipv4
neighbor 172.16.100.132 peer-group backbone-ipv4
neighbor 172.16.100.133 peer-group backbone-ipv4
neighbor 2601:100:c800:100::129 peer-group backbone-ipv6
neighbor 2601:100:c800:100::130 peer-group backbone-ipv6
neighbor 2601:100:c800:100::131 peer-group backbone-ipv6
neighbor 2601:100:c800:100::132 peer-group backbone-ipv6
neighbor 2601:100:c800:100::133 peer-group backbone-ipv6
address-family ipv4 unicast
neighbor 172.16.100.129 activate
neighbor 172.16.100.130 activate
neighbor 172.16.100.131 activate
neighbor 172.16.100.132 activate
neighbor 172.16.100.133 activate
no neighbor 2601:100:c800:100::129 activate
no neighbor 2601:100:c800:100::130 activate
no neighbor 2601:100:c800:100::131 activate
no neighbor 2601:100:c800:100::132 activate
no neighbor 2601:100:c800:100::133 activate
address-family ipv6 unicast
neighbor backbone-ipv6 next-hop-self
neighbor 2601:100:c800:100::129 activate
neighbor 2601:100:c800:100::130 activate
neighbor 2601:100:c800:100::131 activate
neighbor 2601:100:c800:100::132 activate
neighbor 2601:100:c800:100::133 activate
!
router ospf 1
router-id 172.16.100.128
passive-interface Loopback0
max-lsa 12000
!
router ospfv3 1
router-id 172.16.100.128
!
address-family ipv6
!
!
line con 0
logging synchronous
line aux 0
line vty 0 4
privilege level 15
login local
transport preferred ssh
transport input all
transport output telnet ssh
!
!
!
end
Putting it all together
Now lets update our original playbook to add a new role that will push the configuration to the bbr01 router. Create a folder under the roles folder called push_configs.
1
2
3
4
5
6
7
8
9
10
11
---
- name: Build Router Configurations
hosts: bbr01
gather_facts: false
connection: network_cli
vars_files:
- vault.yml
roles:
- role: generate_configs
- role: push_configs
in the roles/push_configs/tasks/main.yml we just need a single task. ios_config allows for you to push a full configuration to the router, and it should only replace only the configuration line that has changed. For this to work your full configuration has to look exactly like the show run command output, otherwise even if nothing is actually changed, I believe you will see a yellow changed on the ansible output.
1
2
3
4
5
- name: Push configs to Cisco Routers and Switches
cisco.ios.ios_config:
src: ./router_configs/.cfg
replace: line
save_when: modified
Now lets re-run the playbook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ansible-playbook pb.build_router_configs.yml --ask-vault-pass
Vault password:
PLAY [Build Router Configurations] **************************************************************************************************************************************************************************************************
TASK [generate_configs : Get data from Nautobot] ************************************************************************************************************************************************************************************
ok: [bbr01 -> localhost]
TASK [generate_configs : Ensure router_configs directory exists] ********************************************************************************************************************************************************************
ok: [bbr01 -> localhost]
TASK [generate_configs : Build Cisco Configurations] ********************************************************************************************************************************************************************************
[DEPRECATION WARNING]: Use 'ansible.utils.ipaddr' module instead. This feature will be removed from ansible.netcommon in a release after 2024-01-01. Deprecation warnings can be disabled by setting deprecation_warnings=False in
ansible.cfg.
changed: [bbr01 -> localhost]
TASK [push_configs : Push configs to Cisco Routers and Switches] ********************************************************************************************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they appear if present in the running configuration on device including the indentation
changed: [bbr01]
PLAY RECAP **************************************************************************************************************************************************************************************************************************
bbr01 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ok that worked, excellent, and here is output from the router after the configuration was pushed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
bbr01#sh run
Building configuration...
Current configuration : 3898 bytes
!
! Last configuration change at 03:36:41 UTC Sun Apr 28 2024 by bbaker4
!
version 15.4
service timestamps debug datetime msec
service timestamps log datetime msec
no service password-encryption
!
hostname bbr01
!
boot-start-marker
boot-end-marker
!
aqm-register-fnf
!
vrf definition MGMT
!
address-family ipv4
exit-address-family
!
!
no aaa new-model
mmi polling-interval 60
no mmi auto-configure
no mmi pvc
mmi snmp-timeout 180
!
!
!
!
!
!
!
!
!
!
!
!
no ip domain lookup
ip domain name example.com
ip cef
ipv6 unicast-routing
ipv6 cef
!
multilink bundle-name authenticated
!
!
!
!
!
!
!
!
username cisco privilege 15 password 0 cisco
!
redundancy
!
!
ip ssh source-interface Ethernet0/3
ip ssh version 2
ip scp server enable
!
!
!
!
!
!
!
!
!
!
!
!
!
interface Loopback0
ip address 172.16.100.128 255.255.255.255
ip ospf 1 area 0
ipv6 address 2601:100:C800:100::128/128
ospfv3 network point-to-point
ospfv3 1 ipv6 area 0
!
interface Ethernet0/0
ip address 172.16.100.0 255.255.255.254
ip ospf network point-to-point
ip ospf 1 area 0
ip ospf cost 100
ipv6 address 2601:100:C800:100::/127
ospfv3 network point-to-point
ospfv3 cost 100
ospfv3 1 ipv6 area 0
!
interface Ethernet0/1
ip address 172.16.100.2 255.255.255.254
ip ospf network point-to-point
ip ospf 1 area 0
ip ospf cost 100
ipv6 address 2601:100:C800:100::2/127
ospfv3 network point-to-point
ospfv3 cost 100
ospfv3 1 ipv6 area 0
!
interface Ethernet0/2
ip address 172.16.100.4 255.255.255.254
ip ospf network point-to-point
ip ospf 1 area 0
ip ospf cost 100
ipv6 address 2601:100:C800:100::4/127
ospfv3 network point-to-point
ospfv3 cost 100
ospfv3 1 ipv6 area 0
!
interface Ethernet0/3
description MGMT-INTERFAC
vrf forwarding MGMT
ip address 192.168.18.101 255.255.255.0
!
router ospfv3 1
router-id 172.16.100.128
!
address-family ipv6 unicast
exit-address-family
!
router ospf 1
router-id 172.16.100.128
max-lsa 12000
passive-interface Loopback0
!
router bgp 100
bgp router-id 172.16.100.128
bgp log-neighbor-changes
neighbor backbone-ipv4 peer-group
neighbor backbone-ipv4 remote-as 100
neighbor backbone-ipv4 update-source Loopback0
neighbor backbone-ipv6 peer-group
neighbor backbone-ipv6 remote-as 100
neighbor backbone-ipv6 update-source Loopback0
neighbor 2601:100:C800:100::129 peer-group backbone-ipv6
neighbor 2601:100:C800:100::130 peer-group backbone-ipv6
neighbor 2601:100:C800:100::131 peer-group backbone-ipv6
neighbor 2601:100:C800:100::132 peer-group backbone-ipv6
neighbor 2601:100:C800:100::133 peer-group backbone-ipv6
neighbor 172.16.100.129 peer-group backbone-ipv4
neighbor 172.16.100.130 peer-group backbone-ipv4
neighbor 172.16.100.131 peer-group backbone-ipv4
neighbor 172.16.100.132 peer-group backbone-ipv4
neighbor 172.16.100.133 peer-group backbone-ipv4
!
address-family ipv4
neighbor backbone-ipv4 next-hop-self
no neighbor 2601:100:C800:100::129 activate
no neighbor 2601:100:C800:100::130 activate
no neighbor 2601:100:C800:100::131 activate
no neighbor 2601:100:C800:100::132 activate
no neighbor 2601:100:C800:100::133 activate
neighbor 172.16.100.129 activate
neighbor 172.16.100.130 activate
neighbor 172.16.100.131 activate
neighbor 172.16.100.132 activate
neighbor 172.16.100.133 activate
exit-address-family
!
address-family ipv6
neighbor backbone-ipv6 next-hop-self
neighbor 2601:100:C800:100::129 activate
neighbor 2601:100:C800:100::130 activate
neighbor 2601:100:C800:100::131 activate
neighbor 2601:100:C800:100::132 activate
neighbor 2601:100:C800:100::133 activate
exit-address-family
!
ip forward-protocol nd
!
!
no ip http server
no ip http secure-server
!
!
!
!
control-plane
!
!
!
!
!
!
!
!
line con 0
logging synchronous
line aux 0
line vty 0 4
privilege level 15
login local
transport preferred ssh
transport input all
transport output telnet ssh
!
!
end
bbr01#
bbr01#
bbr01#
bbr01#sh ip bgp sum
BGP router identifier 172.16.100.128, local AS number 100
BGP table version is 112, main routing table version 112
11 network entries using 1540 bytes of memory
19 path entries using 1520 bytes of memory
3/3 BGP path/bestpath attribute entries using 432 bytes of memory
3 BGP AS-PATH entries using 72 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 3564 total bytes of memory
BGP activity 45/34 prefixes, 84/65 paths, scan interval 60 secs
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
172.16.100.129 4 100 14191 14209 112 0 0 1w1d 0
172.16.100.130 4 100 13398 13388 112 0 0 1w1d 7
172.16.100.131 4 100 13403 13373 112 0 0 1w1d 7
172.16.100.132 4 100 13372 13373 112 0 0 1w1d 2
172.16.100.133 4 100 13386 13382 112 0 0 1w1d 3
bbr01#sh bgp ipv6 uni sum
bbr01#sh bgp ipv6 uni summary
BGP router identifier 172.16.100.128, local AS number 100
BGP table version is 1, main routing table version 1
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
2601:100:C800:100::129
4 100 2 2 1 0 0 00:00:38 0
2601:100:C800:100::130
4 100 2 2 1 0 0 00:00:38 0
2601:100:C800:100::131
4 100 2 2 1 0 0 00:00:38 0
2601:100:C800:100::132
4 100 2 2 1 0 0 00:00:38 0
2601:100:C800:100::133
4 100 2 2 1 0 0 00:00:37 0
bbr01#sh ip os
bbr01#sh ip ospf n
bbr01#sh ip ospf ne
bbr01#sh ip ospf neighbor
Neighbor ID Pri State Dead Time Address Interface
172.16.100.132 0 FULL/ - 00:00:35 172.16.100.5 Ethernet0/2
172.16.100.129 0 FULL/ - 00:00:39 172.16.100.3 Ethernet0/1
172.16.100.130 0 FULL/ - 00:00:35 172.16.100.1 Ethernet0/0
bbr01#sh os
bbr01#sh ospfv3 nei
bbr01#sh ospfv3 neighbor
OSPFv3 1 address-family ipv6 (router-id 172.16.100.128)
Neighbor ID Pri State Dead Time Interface ID Interface
172.16.100.132 0 FULL/ - 00:00:33 3 Ethernet0/2
172.16.100.129 0 FULL/ - 00:00:38 4 Ethernet0/1
172.16.100.130 0 FULL/ - 00:00:38 3 Ethernet0/0
bbr01#sh ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
E1 - OSPF external type 1, E2 - OSPF external type 2
i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
ia - IS-IS inter area, * - candidate default, U - per-user static route
o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
a - application route
+ - replicated route, % - next hop override
Gateway of last resort is 172.16.100.132 to network 0.0.0.0
B* 0.0.0.0/0 [200/0] via 172.16.100.132, 1w1d
172.16.0.0/16 is variably subnetted, 38 subnets, 3 masks
O IA 172.16.30.0/31 [110/300] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.2/31 [110/300] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.4/31 [110/400] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.6/31 [110/300] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.8/31 [110/400] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.128/32 [110/201] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.129/32 [110/301] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.130/32 [110/301] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.30.131/32 [110/301] via 172.16.100.1, 1w1d, Ethernet0/0
C 172.16.100.0/31 is directly connected, Ethernet0/0
L 172.16.100.0/32 is directly connected, Ethernet0/0
C 172.16.100.2/31 is directly connected, Ethernet0/1
L 172.16.100.2/32 is directly connected, Ethernet0/1
C 172.16.100.4/31 is directly connected, Ethernet0/2
L 172.16.100.4/32 is directly connected, Ethernet0/2
O 172.16.100.6/31 [110/200] via 172.16.100.3, 1w1d, Ethernet0/1
O 172.16.100.8/31 [110/200] via 172.16.100.3, 1w1d, Ethernet0/1
O 172.16.100.10/31 [110/200] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.100.12/31 [110/200] via 172.16.100.1, 1w1d, Ethernet0/0
O IA 172.16.100.14/31 [110/300] via 172.16.100.1, 1w1d, Ethernet0/0
O 172.16.100.16/31 [110/200] via 172.16.100.5, 1w1d, Ethernet0/2
O 172.16.100.18/31 [110/300] via 172.16.100.3, 1w1d, Ethernet0/1
C 172.16.100.128/32 is directly connected, Loopback0
O 172.16.100.129/32 [110/101] via 172.16.100.3, 1w1d, Ethernet0/1
O 172.16.100.130/32 [110/101] via 172.16.100.1, 1w1d, Ethernet0/0
O 172.16.100.131/32 [110/201] via 172.16.100.1, 1w1d, Ethernet0/0
O 172.16.100.132/32 [110/101] via 172.16.100.5, 1w1d, Ethernet0/2
O 172.16.100.133/32 [110/201] via 172.16.100.3, 1w1d, Ethernet0/1
B 172.16.220.0/24 [200/0] via 172.16.100.132, 1w1d
B 172.16.221.0/24 [200/0] via 172.16.100.133, 2d07h
B 172.16.222.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.223.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.224.0/24 [200/0] via 172.16.100.133, 2d07h
B 172.16.225.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.226.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.227.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.229.0/24 [200/0] via 172.16.100.130, 2d07h
B 172.16.230.0/24 [200/0] via 172.16.100.130, 2d00h
bbr01#
Utilizing Nautobot as an inventory in Ansible and subsequently generating router configurations based on the data as a Source of Truth is a sophisticated approach to managing and deploying network infrastructure. By leveraging this methodology, one can ensure consistency and accuracy in the configuration process.
In the upcoming post, we will explore the process of setting up Proxmox virtual machines using the same Nautobot Dynamic inventory, taking advantage of Ansible’s automation capabilities. This will enable users to create and manage virtual machines in a more efficient and streamlined manner.