Dynamic Ansible Inventory with Netbox
Posted on
I've recently started using the netbox.netbox.nb_inventory for dynamically creating Ansible inventories.
This will for instance fetch all active devices which has the tag network_edge:
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.example.net
token: token_from_netbox
query_filters:
  - status: active
  - tag: network_edge
Let's check what data we get back (very much truncated):
$ ansible-inventory -v --list -i inventory/netbox.yml{
    "_meta": {
        "hostvars": {
            "router1": {
                "ansible_host": "2001:db8::1",
                "platforms": [
                    "cisco-ios-xr"
                ],
                "primary_ip4": "192.0.2.1",
                "primary_ip6": "2001:db8::1",
                "status": {
                    "label": "Active",
                    "value": "active"
                },
                "tags": [
                    "network_edge"
                ]
            },
            "router2": {
                "ansible_host": "2001:db8::2",
                "platforms": [
                    "arista-eos"
                ],
                "primary_ip4": "192.0.2.2",
                "primary_ip6": "2001:db8::2",
                "status": {
                    "label": "Active",
                    "value": "active"
                },
                "tags": [
                    "network_edge"
                ]
            },
        }
    },
    "all": {
        "children": [
            "ungrouped"
        ]
    },
    "ungrouped": {
        "hosts": [
            "router1",
            "router2"
        ]
    }
}
Unfortunately we can't pass this inventory to Ansible directly, since Ansible needs the ansible_network_os and ansible_connection variables.
The method I ended up with takes the platform from the output above, groups the devices together and then uses group_vars to set the connection settings.
Grouping the devices
We can use the keyed_groups option in the plugin to group devices together, based on something (in this case the platform). Something like this:
keyed_groups:
  - key: platform.slug
    prefix: platform
This would give the following output:
{
    "_meta": {
        "hostvars": {
            "router1": {
                "ansible_host": "2001:db8::1",
                "platforms": [
                    "cisco-ios-xr"
                ],
                "primary_ip4": "192.0.2.1",
                "primary_ip6": "2001:db8::1",
                "status": {
                    "label": "Active",
                    "value": "active"
                },
                "tags": [
                    "network_edge"
                ]
            },
            "router2": {
                "ansible_host": "2001:db8::2",
                "platforms": [
                    "arista-eos"
                ],
                "primary_ip4": "192.0.2.2",
                "primary_ip6": "2001:db8::2",
                "status": {
                    "label": "Active",
                    "value": "active"
                },
                "tags": [
                    "network_edge"
            }
        }
    },
    "all": {
        "children": [
            "ungrouped",
            "platform_arista_eos",
            "platform_cisco_ios_xr"
        ]
    },
    "platform_arista_eos": {
        "hosts": [
            "router2"
        ]
    },
    "platform_cisco_ios_xr": {
        "hosts": [
            "router1"
        ]
    }
}
Ansible group variables
There's already documentation available in the Ansible documentation about group variables, what they are and how they work.
But for our purpose we can use the groups platform_arista_eos and platform_cisco_ios_xr coming from the inventory to set connection settings, per platform.
Let's create the directories inventory/group_vars/platform_arista_eos and inventory/group_vars/platform_cisco_ios_xr, then create the file settings.yml inside both of them. I decided to put the following content into the files:
Arista:
ansible_network_os: arista.eos.eos
ansible_connection: ansible.netcommon.httpapi
proxy_env:
  http_proxy: http://proxy.example.com:8080
IOS-XR:
ansible_network_os: cisco.iosxr.iosxr
ansible_connection: ansible.netcommon.network_cli
ansible_iosxr_commit_comment: Committed by Ansible
Settings for the supported platforms can be found here.
Final execution
Let's run the ansible-inventory command one last time and check the output:
{
    "_meta": {
        "hostvars": {
            "router1": {
                "ansible_connection": "ansible.netcommon.network_cli",
                "ansible_host": "2001:db8::1",
                "ansible_iosxr_commit_comment": "Committed by Ansible",
                "ansible_network_os": "cisco.iosxr.iosxr",
                "platforms": [
                    "cisco-ios-xr"
                ],
                "primary_ip4": "192.0.2.1",
                "primary_ip6": "2001:db8::1",
                "status": {
                    "label": "Active",
                    "value": "active"
                },
            },
            "router2": {
                "ansible_connection": "ansible.netcommon.httpapi",
                "ansible_host": "2001:db8::2",
                "ansible_network_os": "arista.eos.eos",
                "platforms": [
                    "arista-eos"
                ],
                "primary_ip4": "192.0.2.2",
                "primary_ip6": "2001:db8::2",
                "proxy_env": {
                    "http_proxy": "http://proxy.example.com:8080"
                },
                "status": {
                    "label": "Active",
                    "value": "active"
                },
                "tags": [
                    "network_edge"
                ]
            }
        }
    }
}
Now the correct connection variables will be set for the devices, and the inventory can be passed to for instance ansible-playbook using the -i flag.
New platforms can be added easily by creating a new directory and a settings file.