Skip to main content

nautobot-ssot-hudu

A Nautobot SSoT integration that pushes data from Nautobot into Hudu.

Direction: Nautobot is the source of truth. Hudu is a Data Target — Nautobot writes Companies, Devices, and related records into Hudu.

Status: Beta. Seven entity types syncing end-to-end with full idempotency, 94 unit tests, validated against a live Hudu self-hosted instance. Not yet on PyPI.

Compatibility: Nautobot 3.0+, Python 3.10+, nautobot-ssot 4.2+, hudu-magic 0.4+.

Architecture

Built on the DiffSync library bundled with nautobot-ssot. The hudu-magic client library handles all Hudu API I/O.

Nautobot ORM ──> Nautobot DiffSync adapter ─┐
                                            ├──> diff ──> Hudu DiffSync adapter ──> hudu-magic ──> Hudu API
                                  Hudu API ─┘   (read current state)

Mapping

Nautobot Hudu Status
tenancy.Tenant Company ✅ name, description (notes)
dcim.Device Asset (per-role layouts + configurable custom field map) ✅ name + custom-field map + role-based layout selection
ipam.Prefix Network ✅ address (CIDR), name, description
ipam.IPAddress IPAddress ✅ address (host), dns_name, description
ipam.VLAN VLAN ✅ vid (1-4094), name, description
dcim.Rack RackStorage ✅ name, height (U), width (in), serial, asset_tag, description, desc_units
dcim.Device rack/position/face RackStorageItem ✅ asset placement: rack_name, start_unit, end_unit, side
dcim.Location (no API equivalent) ⚠️ Hudu does not expose Locations as a CRUD entity. Per-device location is captured via device_field_map["Location"] = "location.name".

Cross-entity linkages (set automatically when both sides are synced):

  • IPAddress → Asset (Hudu IP page shows the device it's assigned to, via Nautobot IPAddress.interface_assignments → device)
  • Network → VLAN (Hudu Network page shows its VLAN, via Nautobot Prefix.vlan)

Identity model:

Entity Identifier Notes
Company (name,) Both sides enforce uniqueness on name
Device (company_name, name) Hudu Asset names unique only within a company
Network (company_name, address) Networks are scoped per-company
IPAddress (company_name, address) address is the host, no mask
VLAN (company_name, vid) 802.1Q tag, two companies can each own VLAN 100
Rack (company_name, name) Same scoping as Device
RackItem (company_name, asset_name) One rack item per asset max

We deliberately use human-readable identifiers (names/vids) across the diff boundary, not Hudu primary keys. If a Hudu record is deleted and recreated, its pk changes but the identifier stays the same — the next sync rebinds by name rather than diffing as "needs update."

Empty-string normalization: Both adapters coerce empty string "" to None when loading. Hudu stores unset fields as null; Nautobot CharField defaults are "". Without coercion every sync would emit spurious updates for blank fields.

Device custom field mapping

Operators choose which Nautobot Device attributes populate which Hudu custom-layout fields via PLUGINS_CONFIG:

PLUGINS_CONFIG = {
    "nautobot_ssot_hudu": {
        "instance_url": "https://acme.huducloud.com",
        "secret_group_name": "Hudu Credentials",
        "asset_layouts": {
            # Default Hudu asset_layout_id for Devices whose role isn't
            # explicitly mapped below. Unset/None → those devices skip.
            "device": 7,
            # Optional per-Nautobot-Role overrides. Keys are role names;
            # values are Hudu asset_layout_ids. Useful for MSPs documenting
            # heterogeneous fleets across multiple Hudu layouts.
            "device_by_role": {
                "router": 8,
                "switch": 9,
                "firewall": 10,
            },
        },
        # Hudu field label -> Nautobot Device attribute path (dotted)
        "device_field_map": {
            "Hostname": "name",
            "Management IP": "primary_ip4.host",
            "Model": "device_type.model",
            "Serial": "serial",
            "Status": "status.name",
            "Location": "location.name",
        },
    }
}

The Hudu asset_layout must already have custom fields with matching labels. Field-resolution uses safe None-propagation: a Device without a primary_ip4 yields None for "Management IP" rather than raising AttributeError.

Limitations

  • Locations don't sync as a separate entity. Hudu's REST API doesn't expose Locations as a manageable resource (verified empirically — /api/v1/locations returns 404, no Locations admin page). Per-device location is captured via the device_field_map["Location"] = "location.name" entry, which appears as a custom-field string on each synced Asset.
  • Layout migration isn't supported. Hudu's API can't move an existing Asset between asset_layouts. If a Nautobot Device's role is reassigned and the new role maps to a different Hudu layout, the diff surfaces it but the writeback logs a warning and skips. Operator must delete + recreate manually.
  • hudu-magic library quirks are documented in development/hudu/HUDU_API_QUIRKS.md — several endpoints reject the lib's auto-paginated ?page=1 parameter, and several update operations are missing from the lib's resource registry. The plugin works around all of them.

Hudu prep

Before the first sync, the operator must:

  1. Create the asset_layout(s) that Devices will live in. Hudu doesn't ship a built-in "Network Device" layout — each operator defines their own. Each layout's custom fields must have labels matching the keys in device_field_map (e.g. "Hostname", "Management IP", "Model", "Serial", "Status", "Location").
  2. Generate an API key in Hudu (Admin → API Keys → New API Key). Scope: Full access. The "Delete data" permission is required if you plan to use hard_delete=True; "View passwords" and "Export data" can stay off — the plugin doesn't read passwords or export bulk data.
  3. Note the layout IDs — visible in Hudu's Admin → Asset Layouts list. You'll wire them into PLUGINS_CONFIG.asset_layouts.device (and optionally device_by_role).

Run-time options

Exposed as Job parameters in the Nautobot UI:

  • dryrun (framework-provided) — calculate the diff but don't write. Default: True. This is the canonical control; we don't redeclare it.
  • hard_delete — when an entity exists in Hudu but no longer in Nautobot, archive it (default, recoverable via Hudu UI) or hard-delete (irreversible).

Development

For static checks:

uv sync --extra dev
uv run pytest
uv run ruff check

For end-to-end testing against a real Nautobot instance, see development/README.md — a self-contained 4-container stack (postgres + redis + nautobot-web + nautobot-worker) with the plugin source bind-mounted for hot-reload and a seed script that creates synthetic Tenants + a Hudu SecretsGroup.

cd development/
cp .env.example .env  &&  $EDITOR .env
make build && make up
make seed

License

Private / proprietary. Not yet published.

Metadata

Release files for nautobot-ssot-hudu 2026.5.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nautobot-ssot-hudu 2026.5.4
File Size Uploaded Provenance
nautobot_ssot_hudu-2026.5.4.tar.gz 272.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for nautobot-ssot-hudu 2026.5.4
File Interpreter ABI Platform
nautobot_ssot_hudu-2026.5.4-py3-none-any.whl Python 3 none any Details

Total release size: 299.7 kB

Release files / nautobot_ssot_hudu-2026.5.4.tar.gz

Download URL nautobot_ssot_hudu-2026.5.4.tar.gz
Size 272.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c6934cff5939d1b384e4c0596c323d34257e34afc899432a202b9e294c66bdbf
BLAKE2b-256 checksum
How to use checksums
3976405ec2be693e6e7db5067283e7a7c80b7e5a2b57f0a01a0979346638367a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / nautobot_ssot_hudu-2026.5.4-py3-none-any.whl

Download URL nautobot_ssot_hudu-2026.5.4-py3-none-any.whl
Size 27.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9604a09209f476e9253ef6b7d39c6d81b812b5489b13e6363de4ac1b574bfdb9
BLAKE2b-256 checksum
How to use checksums
eb3910bffe2ba5d8bb2ab973e36c292307ff7b342c20ae9b4b8ba64bd86d91a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for nautobot-ssot-hudu 2026.5.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

2026.5.4 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page