Add static DNS (v2 site API) to the legacy adapter - #18
Merged
Conversation
Controller-local DNS records are how a name resolves on the LAN without any public zone
being involved, and they were reachable from nothing but curl. Homelab needs them to
declare internal names as IaC (Homelab#314) and to stop LAN access to the
Pangolin-fronted zones depending on NAT hairpin (Homelab#419).
They live on the v2 site API, which is a different surface from everything the adapter
covered so far and does not share its conventions:
- responses are bare JSON arrays/objects, not the {meta,data} envelope
- an update is a FULL REPLACEMENT; a PUT carrying only the changed field is answered
400 Validation failed, where the legacy rest/user path accepts exactly that
- it hangs off /proxy/network/v2/api/site/<site>, outside the legacy site base
So UnifiStaticDnsRecord is non-nullable throughout while the legacy DTOs are nullable:
on v2 a partial is not something you can send, and the type should not imply it is.
Every field serializes even at its default, which a test pins.
UnifiLegacyOptions gains Site (parsed from the base URL rather than assumed) and
SiteV2Url; UnifiLegacySession gains SendAbsoluteAsync, since a v2 path cannot be reached
relative to the legacy base. Auth, TLS handling and the 401 policy are unchanged and
shared.
Verified against the live controller before writing any of it: POST returns the created
record with _id at status 200, PUT with a full body replaces, PUT with a partial is
rejected, DELETE returns 200 with an empty body. Wildcard keys are supported and match
arbitrary labels. A read-only live test covers the v2 path end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Controller-local DNS records are how a name resolves on the LAN without any public zone being involved, and they were reachable from nothing but
curl. Homelab needs them to declare internal names as IaC (Homelab#314) and to stop LAN access to the Pangolin-fronted zones depending on NAT hairpin (Homelab#419).The v2 surface is not the legacy surface
Static DNS lives on the v2 site API, which shares the host and the auth but none of the conventions:
…/api/s/<site>…/v2/api/site/<site>{ "meta": {...}, "data": [...] }400 Validation failedTreating them alike compiles and passes every unit test, then fails against a real controller — so the difference is encoded in the types rather than left to a caller to remember.
UnifiStaticDnsRecordis non-nullable throughout where the legacy DTOs are nullable: on v2 a partial is not something you can send, and the type shouldn't suggest otherwise. A test pins that every field serializes even at its default value, because the omit-nulls serializer would otherwise quietly thin the body into a rejected partial.Verified before writing any of it
Probed against the live controller with a throwaway record, then deleted:
Wildcards are supported and match arbitrary labels —
*.topaz.local.devalready answersrandomtest123.topaz.local.devon this controller. That's what makes Homelab#419 three records instead of the sixteen it estimated.Shape
UnifiLegacyOptionsgainsSite(parsed from the base URL rather than assumed) andSiteV2Url.UnifiLegacySessiongainsSendAbsoluteAsync— a v2 path can't be reached relative to the legacy base. Auth, TLS handling and the 401 policy are unchanged and shared.UnifiLegacyClientgainsListStaticDnsAsync/CreateStaticDnsAsync/UpdateStaticDnsAsync/DeleteStaticDnsAsync.Unlike
rest/user, this one does get a delete: a DNS record carries no history worth preserving, so there's no reversible middle state to prefer.Tests
36 total, 33 passing / 3 skipped (the destructive ones, correctly gated on session auth). Includes a read-only live test that exercises the whole v2 path against the real controller.
Bumps
Chrison.UnifiSharpto 0.3.0.Chrison.UnifiSharp.Apistays at the already-published10.4.57, so this release has none of the dependency-indexing race 0.2.0 hit.🤖 Generated with Claude Code