Skip to content
LinkState
Go back

From host_vars sprawl to an object graph

Introduction to NetBox/Nautobot Refactoring

NetBox and its fork Nautobot are open‑source IPAM/DCIM platforms built on Django. They provide a relational model for network objects such as devices, device types, sites, racks, interfaces, IP addresses, VLANs, VRFs, and circuits. Core features include:

Both platforms store data in PostgreSQL, enforce referential integrity, and expose a consistent object model that can be version‑controlled via migrations.

Benefits of Refactoring Network Inventory

Moving from per‑device YAML files with freeform variables to a centralized NetBox/Nautobot model yields:


Understanding Current Network Inventory

YAML Device Configuration

Each device is described by a standalone YAML file named after its hostname (e.g., leaf01.yaml). A typical file:

hostname: leaf01
role: leaf
site: dc1
rack: R12
position: 20
vendor: arista
model: vEOS
os_version: 4.27.2F
interfaces:
  - name: Ethernet1
    description: uplink-to-spine1
    enabled: true
    speed: 10000
    mode: routed
    ip_addresses:
      - 10.0.0.1/31
  - name: Ethernet2
    description: server‑face
    enabled: true
    speed: 1000
    mode: access
    vlans:
      - 10

Freeform variables (e.g., bgp_asn, loopback_ip) are often added at the bottom without a schema, relying on naming conventions (*_loopback, *_mgmt) for interpretation.

Freeform Variables and Naming Conventions

Operators embed semantics in variable names:

These conventions live only in tribal knowledge or scattered README files, making onboarding error‑prone.

Limitations of Current Approach

  1. No referential integrity – VLAN IDs cannot be automatically cross‑checked.
  2. Duplication – site, rack, device type repeated across files → inconsistency on rename.
  3. Limited querying – finding all devices with a VLAN requires grepping hundreds of files.
  4. Fragile change propagation – updating a device type model needs manual edits in every referencing file.
  5. No built‑in audit – reliance on external Git history, which may miss automated updates.
  6. Scaling pain – parsing 10 000 YAML files on each refresh can take minutes and consume significant CPU.

NetBox/Nautobot Object Modeling

Devices and Device Types

Example API calls

# Create device type for Arista vEOS
curl -s -X POST "$NETBOX_URL/api/dcim/device-types/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "vEOS",
        "vendor": {"name": "Arista"},
        "u_height": 1,
        "is_full_depth": false
      }' | jq .

# Obtain the newly created device type ID
DT_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/device-types/?model=vEOS" | jq -r '.results[0].id')

# Create the device
curl -s -X POST "$NETBOX_URL/api/dcim/devices/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
        \"name\": \"leaf01\",
        \"device_type\": $DT_ID,
        \"site\": {\"name\": \"dc1\"},
        \"rack\": {\"name\": \"R12\"},
        \"position\": 20,
        \"status\": \"active\"
      }" | jq .

Sites and Locations

Example: rack inside a location

# Assume site dc1 already exists (ID=1)
LOC_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/locations/?name=BuildingA&site=dc1" | jq -r '.results[0].id')
RACK_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/racks/?name=R12&site=dc1&location=$LOC_ID" | jq -r '.results[0].id')

VLANs and VRFs

Example: create VLAN 10 for site dc1

SITE_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/sites/?name=dc1" | jq -r '.results[0].id')
curl -s -X POST "$NETBOX_URL/api/ipam/vlans/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
        \"vid\": 10,
        \"name\": \"users\",
        \"site\": $SITE_ID,
        \"status\": \"active\"
      }" | jq .

IPAM and Prefixes

Example: allocate a /30 prefix and assign first address to an interface

# Create prefix 10.0.0.0/30 in VRF default (VRF ID 1)
PREFIX_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/ipam/prefixes/?network=10.0.0.0/30" | jq -r '.results[0].id')
# Assign first usable address to interface Ethernet1 of leaf01 (assume interface ID 42)
curl -s -X POST "$NETBOX_URL/api/ipam/ip-addresses/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
        \"address\": \"10.0.0.1/31\",
        \"status\": \"active\",
        \"assigned_object_type\": \"dcim.interface\",
        \"assigned_object_id\": 42
      }" | jq .

Relationships and Derived Intent

Device Interconnections and Interfaces

Example: cable between leaf01:Ethernet1 and spine1:Ethernet5

# Get interface IDs
LEAF_IF_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/interfaces/?device=leaf01&name=Ethernet1" | jq -r '.results[0].id')
SPINE_IF_ID=$(curl -s -H "Authorization: Token $TOKEN" "$NETBOX_URL/api/dcim/interfaces/?device=spine1&name=Ethernet5" | jq -r '.results[0].id')
# Create cable
curl -s -X POST "$NETBOX_URL/api/dcim/cables/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
        \"termination_a_type\": \"dcim.interface\",
        \"termination_a_id\": $LEAF_IF_ID,
        \"termination_b_type\": \"dcim.interface\",
        \"termination_b_id\": $SPINE_IF_ID,
        \"type\": \"cat6a\",
        \"status\": \"connected\",
        \"length\": 5,
        \"length_unit\": \"m\"
      }" | jq .

Power and Network Feeds

Example: assign a redundant power feed to a rack

# Assume power feed PF-R12-A already exists (ID=7)
curl -s -X POST "$NETBOX_URL/api/dcim/power-outlets/" \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
        \"name\": \"Outlet1\",
        \"device\": {\"name\": \"leaf01\"},
        \"type\": \"iec-60320-c13\",
        \"feed\": 7
      }" | jq .

Intent-Based Networking and Validation

NetBox/Nautobot can store derived intent as computed properties or via Custom Fields and Validation Rules.

# Pseudocode for a validation job (Nautobot)
def validate_interface(intent):
    if intent.custom_field_data.get('intent_server_face'):
        if intent.mode != 'access':
            raise ValidationError("Server‑facing intent requires access mode")
        if not any(v.vid in range(10,21) for v in intent.tagged_vlans.all()):
            raise ValidationError("Server‑facing intent must have VLAN 10‑20 tagged")

The job runs in the background, flags violations in the UI, and can block status changes via Object Change hooks.


First‑Class Modeling vs Generated Facts

Identifying Key Network Attributes

Attributes that benefit from first‑class modeling

Attributes that can remain generated (derived on‑the‑fly)

Device and Interface Attributes

AttributeFirst‑Class?Reason
device.nameYesPrimary key for lookup
device.device_typeYesEnforces model constraints (e.g., console port count)
device.siteYesEnables site‑scoped reports
device.rack & positionYesPhysical placement, power/cooling calculations
device.custom_field_data (e.g., bgp_asn)ConditionalPromote to CF if frequently queried; otherwise generate
interface.nameYesUnique per device
interface.typeYesDetermines speed, duplex, supported modules
interface.enabledYesOperational state
interface.mode (access/trunk/routed)YesDrives VLAN handling
interface.mac_addressYesNeeded for L2/L3 forwarding, ACLs, and change‑of‑authentication checks

End of document.


Share this post on:

Previous Post
Reading FDB aging like an outage timeline
Next Post
How much cloud belongs in your network SoT