Generates and deploys Kea DHCP4 static reservations for printer devices by querying NetBox as the authoritative source of truth. The role reads device role, site, MAC addresses, and primary IPv4 from NetBox, renders a JSON reservations file, validates it, and hot-reloads the Kea DHCP4 service on the target host.
Designed to run as Phase 2 after the fortigate_dhcp_reconcile role has verified NetBox data accuracy.
- Ansible ≥ 2.20
- Ubuntu Resolute on the target host
- ISC Kea DHCP4 installed and running on the target host
- The target host must have
kea-dhcp4accessible (for config validation) - The Ansible controller must have network access to the NetBox API
vault_netbox_tokenset ingroup_vars/all/vault.yml(ansible-vault encrypted)netbox_urlset ingroup_vars/all/main.yml
All variables have defaults defined in defaults/main.yml.
| Variable | Default | Description |
|---|---|---|
netbox_url |
http://netbox.example.local |
Base URL of your NetBox instance |
netbox_token |
{{ vault_netbox_token }} |
NetBox API token — must come from vault |
netbox_validate_certs |
false |
Whether to validate TLS certificates |
netbox_api_timeout |
30 |
HTTP timeout in seconds for NetBox API calls |
Note: The vault variable must be named
vault_netbox_token. Do not usevault_netbox_api_token— that name is not recognised by this role.
| Variable | Default | Description |
|---|---|---|
netbox_site_slug |
main |
NetBox site slug to filter devices by |
netbox_printer_role_slugs |
["printer", "printer-scheduled"] |
List of NetBox device role slugs to include |
netbox_printer_status |
active |
NetBox device status filter. Valid values: active, planned, staged, failed, inventory, decommissioning |
| Variable | Default | Description |
|---|---|---|
kea_reservations_dir |
/etc/kea/reservations |
Directory on the Kea server where the reservations file is deployed |
kea_reservations_filename |
printers.json |
Filename for the reservations JSON file |
kea_dhcp4_config |
/etc/kea/kea-dhcp4.conf |
Path to the main Kea DHCP4 configuration file |
kea_dhcp4_service |
kea-dhcp4 |
systemd service name for Kea DHCP4 |
kea_reservations_owner |
_kea |
Owner of the deployed reservations file |
kea_reservations_group |
_kea |
Group of the deployed reservations file |
| Variable | Default | Description |
|---|---|---|
kea_validate_config |
true |
Run kea-dhcp4 -t config validation after deploying the file. Set to false to skip (useful when testing before Kea is installed). |
kea_service_manage |
true |
Whether to reload the Kea service after a successful deploy. Set to false to deploy the file without touching the service. Mirrors kea_service_manage in ansible-role-kea-dhcp so host_vars controls both. |
netbox_require_reservations |
true |
Fail the play when NetBox returns zero printers or zero valid reservations. Set to false to allow empty results (useful in staging environments). |
The role runs three steps in sequence:
Step 0 — Pre-flight checks (tasks/preflight.yml, tags: always)
Validates every required variable before making any API call or touching any file. Fails immediately with a descriptive, actionable error message so nothing is left in a partial state. Checks include:
ansible-coreversion is>= 2.20- NetBox URL, API token, and API timeout are set and well-formed
- NetBox site slug, printer role slugs, and status filter are set and valid
- Kea reservations directory, filename, config path, service name, and file owner/group are set and well-formed
Step 1 — Query NetBox (tasks/query_netbox.yml)
Queries the NetBox API from the Ansible controller (delegate_to: localhost) so the Kea server never needs direct access to NetBox. Fetches all matching printer devices and their interfaces, builds a device-ID-to-MAC-address map, then constructs a list of Kea reservation entries. Devices missing a primary IPv4 address or an interface with a MAC address are skipped with a warning.
Step 2 — Deploy reservations (tasks/deploy_reservations.yml)
Runs on the Kea server target. Creates the reservations directory, renders the JSON file from the kea_reservations.json.j2 template, and validates it with Python's json.load inline. Also renders a README.md (from README.md.j2) into the same directory, explaining what the JSON file is, that Kea does not read it directly, and how a consuming playbook actually gets its contents into the live config — see Integration with ansible-role-kea-dhcp. If kea_validate_config is true, the full DHCP4 config is validated with kea-dhcp4 -t. A successful deploy notifies the Reload kea-dhcp4 handler, which hot-reloads the service if kea_service_manage is true.
This role only writes {{ kea_reservations_dir }}/{{ kea_reservations_filename }}
(e.g. /etc/kea/reservations/printers.json) — it does not modify
kea-dhcp4.conf itself and does not use Kea's <?include?> directive. For
the reservations in that file to actually take effect, the subnet in
ansible-role-kea-dhcp's kea_dhcp4_subnets must list this file under
reservation_files, e.g.:
kea_dhcp4_subnets:
- id: 1
subnet: "192.168.1.0/24"
reservation_files:
- printers.json # must match kea_reservations_filename belowansible-role-kea-dhcp's merge_reservations.yml reads this file's
current content on the target host and fully inlines it into
kea-dhcp4.conf at render time — nothing is included by reference. This
means this role must run before ansible-role-kea-dhcp in the same
playbook for a reservations update to reach the live config; running
this role on its own (or after ansible-role-kea-dhcp already rendered
the config) updates the file on disk but does not, by itself, change what
Kea is currently serving. This role's own Reload kea-dhcp4 handler only
reloads Kea with whatever kea-dhcp4.conf already contains — it does not
cause ansible-role-kea-dhcp to re-render that file.
If kea_dhcp4_subnets does not list this file in reservation_files for
any subnet, the file is written but never read — the same disconnect this
integration was written to fix.
- name: "Kea Printer Reservations — Query NetBox and deploy to Kea"
hosts: kea_servers
gather_facts: false
become: true
roles:
- role: netbox_printer_reservationsWith overrides:
- name: "Kea Printer Reservations — Query NetBox and deploy to Kea"
hosts: kea_servers
gather_facts: false
become: true
roles:
- role: netbox_printer_reservations
vars:
netbox_site_slug: "branch-office"
netbox_printer_role_slugs:
- "printer"
- "mfp"
kea_validate_config: false
kea_service_manage: falseRun together with ansible-role-kea-dhcp so a NetBox change actually
reaches the live config — this role first, so the file is fresh before
ansible-role-kea-dhcp reads and inlines it:
- name: "Deploy Kea DHCP4 with NetBox printer reservations"
hosts: kea_servers
become: true
roles:
- role: netbox_printer_reservations
- role: ansible-role-kea-dhcp
vars:
kea_dhcp4_subnets:
- id: 1
subnet: "192.168.1.0/24"
pools:
- pool: "192.168.1.100 - 192.168.1.200"
reservation_files:
- printers.json# Normal run
ansible-playbook netbox_printer_reservations.yml
# Dry-run (check mode)
ansible-playbook netbox_printer_reservations.yml --check
# Override site at runtime
ansible-playbook netbox_printer_reservations.yml -e netbox_site_slug=main1. Store the NetBox API token in vault:
# Add to group_vars/all/vault.yml
vault_netbox_token: "your-netbox-api-token-here"
# Encrypt the file
ansible-vault encrypt group_vars/all/vault.yml2. Set the NetBox URL in group vars:
# group_vars/all/main.yml
netbox_url: "https://netbox.example.com"3. Verify your NetBox slugs:
- Site slugs:
GET {{ netbox_url }}/api/dcim/sites/ - Device role slugs:
GET {{ netbox_url }}/api/dcim/device-roles/
Each printer device in NetBox must have:
- A primary IPv4 address assigned
- At least one interface with a MAC address
- The correct site and device role slug
- Status matching
netbox_printer_status(default:active)
Devices missing either a primary IP or a MAC address are skipped and logged as warnings, not errors (unless netbox_require_reservations: true results in an empty reservation list).
None. This role has no Ansible Galaxy dependencies.
It is designed to be run after the fortigate_dhcp_reconcile role to ensure NetBox data is accurate before generating DHCP reservations, and before ansible-role-kea-dhcp in the same playbook — see Integration with ansible-role-kea-dhcp above.
MIT — Copyright (c) 2026 Bob Tanner
Bob Tanner — Real Time Enterprises, Inc.