Usage:

pip install pynetbox ansible

ansible all -i hosts/env -m setup --tree /tmp/facts/env
#!/opt/netbox/bin/python

import argparse
import json
import os
import sys

import pynetbox
import yaml

import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

if sys.version_info < (3, 6):
    print("Python 3.6 is required")
    sys.exit(2)


def to_json(in_dict):
    return json.dumps(in_dict, sort_keys=True, indent=4)


def load_configuration(path="/etc/ansible/netbox.yml"):
    """ Load netbox configuration

        /etc/ansible/netbox.yml
    """
    try:
        with open(path, "r") as fd:
            return yaml.safe_load(fd)
    except yaml.YAMLError as yml_error:
        print(yml_error)


NETBOX_ENDPOINT = load_configuration()["netbox_endpoint"]
NETBOX_TOKEN = load_configuration()["netbox_token"]

if not NETBOX_ENDPOINT:
    raise OSError("environmet var NETBOX_ENDPOINT not set")
if not NETBOX_TOKEN:
    raise OSError("environmet var NETBOX_TOKEN not set")

nb = pynetbox.api(NETBOX_ENDPOINT, NETBOX_TOKEN, ssl_verify=False)


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--list', action='store_true')
    parser.add_argument('--host', action='store')
    parser.add_argument('--introspection', action='store_true')
    return parser.parse_args()


def get_site(site_name):
    site = nb.dcim.sites.get(name=site_name)
    return site


def get_vms(site_name):
    platform = nb.dcim.platforms.get(name=site_name)
    vms = nb.virtualization.virtual_machines.filter(platform_id=platform.id)
    return vms


def get_devices(site_name):
    platform = nb.dcim.platforms.get(name=site_name)
    devices = nb.dcim.devices.filter(platform_id=platform.id)
    return devices


def get_roles():
    roles = nb.dcim.device_roles.all()
    return roles


def get_role(server):
    try:
        try:
            return server.role
        except:
            return server.device_role
    except Exception as error:
        return "ungrouped"


def which_tenant(server):
    try:
        if server.tenant:
            return str(server.tenant)
        else:
            # print("No tenant")
            return None
    except Exception as error:
        print(error)


def which_gmn(server):
    try:
        if "management" in server.tags:
            return "management"
        else:
            return "no_management"
    except Exception as error:
        print(error)


def get_tenants():
    tenants = nb.tenancy.tenants.all()
    return tenants


def get_device_role(site, role_name):
    platform = nb.dcim.platforms.get(name=site)
    role = nb.dcim.device_roles.get(platform_id=platform.id, name=role_name)
    if not role:
        print("Role not found")
        sys.exit(2)
    return role.id


def get_vip(site, servers):
    vms = get_vms(site)

    proxy_vms = []
    for vm in vms:
        if str(vm.role).lower() == "proxy-servers":
            proxy_vms.append(vm)

    for vm in proxy_vms:
        ips = nb.ipam.ip_addresses.filter(virtual_machine_id=vm.id)
        for ip in ips:
            if str(ip.role) == "VIP":
                return ip.address
    else:
        return ""


def get_tenant_role_map(path="/etc/ansible/role-tenant-map.json"):
    """  """
    try:
        with open(path, "r") as fd:
            return json.load(fd)
    except Exception as error:
        print(error)


def generate_inventory(site, servers):
    """ Generate ansible groups based on roles and tenants
    """
    role_tenant_map = get_tenant_role_map()
    roles = get_roles()
    tenants = get_tenants()

    # base json|yaml structure
    inventory = {
        "all": {"hosts": []},
        "management": {"children": {}},
        "no_management": {"children": {}, "vars": {}},
        "ungrouped": {"children": {}},
        "_meta": {"hostvars": {}}
    }

    proxy_vip = get_vip(site, servers).split("/")[0]
    if proxy_vip:
        inventory["no_gmn"]["vars"] = {
            "ansible_ssh_common_args": f"'-o ProxyJump={proxy_vip} -o StrictHostKeyChecking=no'"
        }

    for tenant in tenants:
        inventory[tenant.name] = {"children": {}}
        for role in role_tenant_map[tenant.name]:
            inventory[tenant.name]["children"][role] = {}

    for role in roles:
        inventory[role.name] = {"hosts": []}
        if role.name == "proxy-servers":
            inventory["management"]["children"][role.name] = {}
        else:
            inventory["no_management"]["children"][role.name] = {}

    for server in servers:
        ip = str(server.primary_ip).split("/")[0]
        role = str(get_role(server))
        inventory["all"]["hosts"].append(server.name)
        inventory["_meta"]["hostvars"][server.name] = {
            "ansible_host": f"{ip}"
        }
        if role != "None":
            inventory[role]["hosts"].append(server.name)

    return to_json(inventory)


def get_introspection_data(site_name):
    """
    site_name: str: SITE1, SITE2, etc.

    return list(dicts)
    [
        {
            "mac": "pxe mac",
            "arch": "x86_64",
            "pm_type": "pxe_ilo",
            "pm_user": "static per site",
            "pm_password": "static per site",
            "pm_address": "IPMI address"
            "name": "position name"
        },
    ]

    steps:
        query devices api on a given site (platform) for computes and controllers
        get pm_user and pm_password from somewhere
        get IPMI address
    """
    openstack_nodes = []
    platform = nb.dcim.platforms.get(name=site_name)
    devices = nb.dcim.devices.filter(platform_id=platform.id)
    for device in devices:
        if device.custom_fields["openstack_device"]:
            d = {
                "name": device.name,
                "mac": [device.custom_fields["openstack_pxeboot_mac"]],
                "pm_type": "pxe_ilo",
                "arch": "x86_64",
                "pm_user": os.environ.get("OS_INTROSPECTION_USER", None),
                "pm_password": os.environ.get("OS_INTROSPECTION_PASSWORD", None)
            }
            interfaces = nb.dcim.interfaces.filter(device_id=device.id)
            for i in interfaces:
                if i.name == "ILO":
                    ilo_ip = nb.ipam.ip_addresses.filter(interface_id=i.id)[0]
                    d["pm_address"] = ilo_ip.address.split("/")[0]
            openstack_nodes.append(d)
    return to_json(openstack_nodes)


if __name__ == "__main__":
    args = parse_args()
    # Are "-" deprecated in group names?
    site_name = os.environ.get("SITE", None)
    if not site_name:
        print("Define a site to query with SITE environment variable")
        sys.exit(2)
    site_name = site_name.upper()

    if args.introspection:
        introspection_data = get_introspection_data(site_name)
        print(introspection_data)
        sys.exit(0)

    devices = get_devices(site_name)
    vms = get_vms(site_name)
    servers = devices + vms

    if args.list:
        hosts = generate_inventory(site_name, servers)
        print(hosts)
    elif args.host:
        pass