Development¶
Requires uv and Docker. PowerShell (pwsh) is optional: with it, the unit tests also
run the real scripts against stub cmdlets.
git clone https://github.com/thomaschristory/netbox-windows-dhcp-plugin
cd netbox-windows-dhcp-plugin
uv sync
Make targets¶
| Target | Does |
|---|---|
make lint |
ruff check and ruff format --check (line length 120) |
make format |
Fix what ruff can and format |
make test |
Unit tests (tests/): the Windows layer, the diff engine, the docs. No NetBox needed |
make dev |
Start the dev stack: NetBox 4.7 with netbox-plugin-dhcp, netbox-branching and netbox-change-control, at http://localhost:8000 |
make test-netbox |
NetBox integration tests (tests_netbox/) inside the dev stack |
make restart |
Restart NetBox and the worker after Python changes (the source is mounted into the containers) |
make logs |
Follow the NetBox log |
make down |
Stop the dev stack |
make dist |
Build the wheel and sdist and check them |
make docs |
Build this site into site/ with Zensical (strict) |
make docs-serve |
Serve the site with live reload |
make test-lab |
Opt-in lab tests against a real Windows server, see below |
make lab-e2e |
Opt-in end-to-end run on the dev stack against that server |
The dev stack runs in branching mode. Its first start migrates a fresh database and takes several minutes. Log in as
admin with the SUPERUSER_PASSWORD from dev/env/netbox.env.
Code layout¶
netbox_windows_dhcp_plugin/
windows/ pure Python: PowerShell scripts, transports (WinRM, SSH), the client (one function per cmdlet),
normalisation, and an in-memory fake server for tests. No Django imports
sync/ diff engine, Windows and NetBox records, push planning and writing, import
models/ api/ graphql/ views.py forms.py ... the NetBox side
connection.py builds a client from a Windows server and its credential profile
jobs.py every job; all Windows I/O happens here
integrations/branching.py the only module that imports netbox-branching and netbox-change-control
The design explains the decisions; the roadmap what is done.
Rules worth knowing before a change:
- Values reach PowerShell only through the
$pJSON literal built bywindows/psscript.py; never format a value into a script. - Never log or store credentials.
- A new cmdlet gets an implementation in
windows/fake.py, a JSON fixture undertests/windows/testdata/and apwshstub test; mark it# unverified:until it has run against a real server. - Model changes need a migration and refreshed query-count baselines in both standalone and branching modes.
- Update
CHANGELOG.mdunder Unreleased with every user-visible change, and these pages when behaviour changes.
Testing tiers¶
- Unit (
make test): script rendering and envelope parsing, normalisation, the client against recorded fixtures and the fake server, the diff engine, and checks on these docs. - PowerShell (part of
make test, skipped withoutpwsh; thePWSHvariable picks the binary): the real scripts run against stub cmdlets intests/windows/testdata/stubs.ps1. - NetBox (
make test-netbox): models, views, API, import, push and leases against the fake server. CI runs it in standalone and branching modes. - Lab (opt-in): a real Windows DHCP server, see below.
CI runs lint, unit tests on Python 3.12 to 3.14, the build, the docs build, and the NetBox tests in both modes.
Lab tests¶
The lab tier checks the client and the whole sync against a real Windows DHCP server. CI never runs it.
Use a lab server
The tests only create objects named nbwd-test-... in 198.18.0.0/15 and 2001:db8:4e42::/64 (MAC addresses
02-4e-42-...), check name and range before every delete, and clean up even when a test fails. Still, never
point them at a production server.
Preparing a lab VM¶
Any Windows Server 2016 or later VM will do; an evaluation edition on a host-only network is enough, since the tests never serve real clients. In an elevated PowerShell on the VM, run the setup script from the repository:
Set-ExecutionPolicy -Scope Process Bypass
.\dev\scripts\lab-server-setup.ps1 -AllowFrom 192.168.56.0/24
It installs the DHCP role, creates a local account nbwd-lab in DHCP Administrators and Remote Management Users
(asking for its password), sets up WinRM over HTTPS on 5986 with a self-signed certificate, opens the firewall
(optionally only to the given network), and prints the settings for the next step. It is safe to run again.
Running them¶
Put the connection settings in dev/env/netbox.env (never committed) or the environment:
| Variable | Meaning |
|---|---|
WINDOWS_DHCP_LAB_HOST |
Host name or address of the lab server |
WINDOWS_DHCP_USERNAME, WINDOWS_DHCP_PASSWORD |
The account |
WINDOWS_DHCP_LAB_PORT |
Optional, default 5986 |
WINDOWS_DHCP_LAB_AUTH |
Optional: ntlm (default), basic or kerberos |
WINDOWS_DHCP_LAB_REALM |
Optional, Kerberos realm |
WINDOWS_DHCP_LAB_INSECURE |
1 skips the certificate check (self-signed lab certificate) |
WINDOWS_DHCP_LAB_RECORD |
1 saves every raw PowerShell answer under .lab/record/, to turn into fixtures after scrubbing |
Then:
make test-lab # the client over WinRM, one round trip per unverified cmdlet
make dev # recreate the dev stack once, so it mounts the lab helpers
make lab-e2e # import, push creates, updates and deletes, leases, on the dev stack
make test-lab writes property dumps to .lab/probe/. make lab-e2e prints PASSED at the end and exits non-zero
on failure. Both leave the server as they found it. Everything under .lab/ can identify the lab and is ignored by
git: scrub host names and addresses before turning a recording into a fixture.
Documentation¶
These pages live in docs/ and are built with Zensical from mkdocs.yml:
make docs-serve # http://localhost:8001, reloads on save
make docs # strict build into site/
make test checks the docs too: no em dashes, the configuration table matches the plugin's defaults, every page is
in the navigation, and the PLUGINS_CONFIG examples are valid. The site is published to GitHub Pages when a release
is tagged.
Releasing¶
- Update
netbox_windows_dhcp_plugin/version.pyand move the Unreleased section ofCHANGELOG.mdunder the new version and date. make dist, merge to main.- Tag
vX.Y.Zon main and push the tag. The release workflow checks the tag is on main, builds, and publishes to PyPI through trusted publishing after a maintainer approves thepypienvironment; the docs workflow publishes the site.