feat: NetBox IP/interface facts with offline cache #506

Open
unkinben wants to merge 1 commits from benvin/netbox-facts into develop
Owner

Why

Nodes need their authoritative IP/interface data from NetBox, but a NetBox outage must never become a fleet-wide puppet failure. Static IPs never change (a replacement is a new machine), so stale cache is always safe.

Changes

  • netbox fact (modules/libs/lib/facter/netbox.rb): queries NetBox (dcim devices + virtualization VMs) by fqdn/hostname; emits source, name, primary_ip, interfaces[] (name, mac, ips, primary), fetched_at, cached.
  • Caches every success to /var/cache/puppet-netbox/facts.json (0600). On any failure (short 3s/5s timeouts, DNS, non-200, parse) it serves the cached payload with cached: true. Never-cached host returns nothing; the fact never raises. No TTL expiry - correctness beats freshness.
  • profiles::netbox::facts: inert until $api_token set; writes root-only /etc/puppetlabs/netbox.token (0600), /etc/puppetlabs/netbox.url, and the cache dir. Confined off (no-op) where the token is absent. Included from profiles::base.
  • stdlib net/http + json only; confined on token-file existence.

Seeding the token (follow-up)

Set the eyaml secret profiles::netbox::facts::api_token (a read-only NetBox API token) in hieradata - common.eyaml for fleet-wide, or scoped to the roles that need it. Until then the fact is a no-op everywhere.

Manual test (one host)

printf '%s\n' "$NETBOX_RO_TOKEN" > /etc/puppetlabs/netbox.token && chmod 600 /etc/puppetlabs/netbox.token
facter -p netbox
# expect interfaces + primary_ip, cached=false
# now blackhole netbox and re-run: cached=true, same data (never fails)

Transform + cache-fallback + never-cached + 0600 verified locally with a stubbed NetBox (17 assertions).

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT

## Why Nodes need their authoritative IP/interface data from NetBox, but a NetBox outage must never become a fleet-wide puppet failure. Static IPs never change (a replacement is a new machine), so stale cache is always safe. ## Changes - `netbox` fact (modules/libs/lib/facter/netbox.rb): queries NetBox (dcim devices + virtualization VMs) by fqdn/hostname; emits `source`, `name`, `primary_ip`, `interfaces[]` (name, mac, ips, primary), `fetched_at`, `cached`. - Caches every success to `/var/cache/puppet-netbox/facts.json` (0600). On any failure (short 3s/5s timeouts, DNS, non-200, parse) it serves the cached payload with `cached: true`. Never-cached host returns nothing; the fact never raises. No TTL expiry - correctness beats freshness. - `profiles::netbox::facts`: inert until `$api_token` set; writes root-only `/etc/puppetlabs/netbox.token` (0600), `/etc/puppetlabs/netbox.url`, and the cache dir. Confined off (no-op) where the token is absent. Included from `profiles::base`. - stdlib net/http + json only; confined on token-file existence. ## Seeding the token (follow-up) Set the eyaml secret `profiles::netbox::facts::api_token` (a read-only NetBox API token) in hieradata - `common.eyaml` for fleet-wide, or scoped to the roles that need it. Until then the fact is a no-op everywhere. ## Manual test (one host) ``` printf '%s\n' "$NETBOX_RO_TOKEN" > /etc/puppetlabs/netbox.token && chmod 600 /etc/puppetlabs/netbox.token facter -p netbox # expect interfaces + primary_ip, cached=false # now blackhole netbox and re-run: cached=true, same data (never fails) ``` Transform + cache-fallback + never-cached + 0600 verified locally with a stubbed NetBox (17 assertions). https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
unkinben added 1 commit 2026-08-05 00:45:41 +10:00
feat: add NetBox IP/interface facts with offline cache
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
fe3c1a1410
Add a structured `netbox` fact that reads this node's IP/interface data
from NetBox, and profiles::netbox::facts to seed its credentials.

- netbox fact: queries NetBox (devices + VMs) by fqdn/hostname, emits
  interfaces (name, mac, ips, primary) and primary_ip.
- Caches every success to /var/cache/puppet-netbox/facts.json (0600).
  On any failure (short timeouts, DNS, non-200, parse) it serves the
  cached payload with cached=true; static IPs never change so stale is
  always safe. A never-cached host returns nothing; the fact never raises.
- profiles::netbox::facts: inert until $api_token is set; writes the
  root-only token/url files and the cache dir. Confined off (no-op) on
  hosts without the token. Included from profiles::base.

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
All checks were successful
ci/woodpecker/pr/ruby-validate Pipeline was successful
Required
Details
ci/woodpecker/pr/puppet-lint Pipeline was successful
Required
Details
ci/woodpecker/pr/yamllint Pipeline was successful
Required
Details
ci/woodpecker/pr/bolt-validate Pipeline was successful
Required
Details
ci/woodpecker/pr/erb-validate Pipeline was successful
Required
Details
ci/woodpecker/pr/epp-validate Pipeline was successful
Required
Details
ci/woodpecker/pr/puppet-validate Pipeline was successful
Required
Details
ci/woodpecker/pr/ruby-check Pipeline was successful
Required
Details
Checking for merge conflicts…
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin benvin/netbox-facts:benvin/netbox-facts
git checkout benvin/netbox-facts
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/puppet-prod#506