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.