-
-
Notifications
You must be signed in to change notification settings - Fork 970
[management] Add custom dns zones #4849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
WalkthroughAdds a DNS zones and records subsystem and integrates multi-zone custom DNS handling into network-map generation; extends store, server wiring, REST client, OpenAPI, gRPC conversion, utilities, activity codes, and adds unit/integration tests across managers, store, controllers, and clients. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as "HTTP API Handler"
participant Perms as "PermissionsMgr"
participant ZonesMgr as "Zones Manager"
participant Store
participant AccountMgr as "AccountMgr / Events"
Client->>API: POST /api/dns/zones (ZoneRequest)
API->>API: auth + decode + validate
API->>Perms: check permission (dns:create)
Perms-->>API: allowed
API->>ZonesMgr: CreateZone(ctx, zone)
ZonesMgr->>Store: Begin Tx, validate groups
Store-->>ZonesMgr: ok
ZonesMgr->>Store: CreateZone, IncNetworkSerial, Commit
Store-->>ZonesMgr: created zone
ZonesMgr->>AccountMgr: Store DNSZoneCreated event
ZonesMgr->>AccountMgr: Trigger background peer updates
ZonesMgr-->>API: created Zone
API-->>Client: 200 (Zone)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
management/server/types/account.go (1)
269-279: Based on my thorough verification of the codebase, I can now provide the final assessment:Both issues in the review comment are confirmed and valid
The code inspection reveals:
Flag dropping is confirmed: Both
Account.GetPeerNetworkMapandNetworkMapBuilder.assembleNetworkMapcreate a newCustomZonewith onlyDomainandRecordsfields:customZones[i] = nbdns.CustomZone{ Domain: customZone.Domain, Records: records, }This leaves
SearchDomainDisabledandSkipPTRProcesszero-initialized (false), dropping the original values.In-place mutation is confirmed: The input
customZonesslice is modified directly in-place. While current call patterns typically pass fresh or empty slices, the function signature permits slice reuse, and any such reuse would result in cumulative filtering across multiple peers.The
CustomZonestruct indns/dns.godefinitively has all four fields (Domain, Records, SearchDomainDisabled, SkipPTRProcess), confirming the impact of this code pattern.
Preserve
CustomZoneflags and avoid surprising slice mutation inGetPeerNetworkMapTwo issues in this block:
Flags are dropped for the account DNS zone
When a zone matches
dns.Fqdn(dnsDomain), the code replaces the entire element:customZones[i] = nbdns.CustomZone{ Domain: customZone.Domain, Records: records, }This zeroes
SearchDomainDisabledandSkipPTRProcess, changing resolver behavior for that zone.At minimum, keep the original flags:
records := filterZoneRecordsForPeers(peer, customZone, peersToConnectIncludingRouters, expiredPeers)customZones[i] = nbdns.CustomZone{Domain: customZone.Domain,Records: records,}
records := filterZoneRecordsForPeers(peer, customZone, peersToConnectIncludingRouters, expiredPeers)customZones[i].Records = records
In‑place mutation can corrupt subsequent calls
GetPeerNetworkMapmutates the passedcustomZonesslice. If callers reuse a single slice for multiple peers, records are progressively intersected (peer 2 sees only a subset derived from peer 1's view).Consider copying before filtering to make the function side‑effect‑free for callers:
- if dnsManagementStatus {
for i, customZone := range customZones {
- if dnsManagementStatus {
filteredZones := slices.Clone(customZones)for i, customZone := range filteredZones { if customZone.Domain == dns.Fqdn(dnsDomain) {
records := filterZoneRecordsForPeers(peer, customZone, peersToConnectIncludingRouters, expiredPeers)customZones[i].Records = records
filteredZones[i].Records = filterZoneRecordsForPeers(peer, customZone, peersToConnectIncludingRouters, expiredPeers) }
}dnsUpdate.CustomZones = customZones
}dnsUpdate.CustomZones = filteredZonesAlso applies to:
NetworkMapBuilder.assembleNetworkMap
🧹 Nitpick comments (8)
management/server/util/util.go (1)
3-6: Domain validation helper is fine; just confirm label policy
IsValidDomainand the compileddomainRegexgive a clear, centralized domain check and cover common cases (including*.example.com).Be aware of two behavioral details:
- Labels with underscores (e.g.
_acme-challenge.example.com) are rejected.- Labels starting/ending with
-are accepted, even though they’re not valid hostnames per RFCs.If that matches your product’s intended constraints, this is good to go; otherwise you may want to tighten/relax the regex accordingly.
Also applies to: 58-63
management/server/http/handlers/peers/peers_handler.go (1)
13-14: Avoid unnecessarydns.CustomZonedependency in handler
GetAccessiblePeersdoesn’t consumeCustomZonesfrom the returned network map, so you can avoid importinggithub.com/netbirdio/netbird/dnsand allocating an empty slice. Passingnilis simpler and keeps DNS concerns encapsulated deeper in the stack.-import ( +import ( ... - "github.com/netbirdio/netbird/dns" ... ) ... - netMap := account.GetPeerNetworkMap(r.Context(), peerID, dnsDomain, []dns.CustomZone{}, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil) + netMap := account.GetPeerNetworkMap(r.Context(), peerID, dnsDomain, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)Also applies to: 324-327
shared/management/http/api/openapi.yml (2)
1765-1786: Clarify whether DNS recordnameis FQDN or zone‑relative
DNSRecordRequest.nameis documented with example"www", which suggests a label relative to the zone:DNSRecordRequest: properties: name: description: DNS record name example: wwwHowever, the server‑side validation in
management/internals/modules/zones/records/manager/manager.gocurrently requiresrecord.NametoHasSuffix(zone.Domain), and the integration test creates records with full FQDNs like"api.test.example.com"for a zone domain"test.example.com".This mismatch can confuse API consumers.
Suggestion
Either:
- Keep the current server behavior (full FQDN) and update the description/example to something like
"www.example.com"/"api.test.example.com", or- Change server validation to accept zone‑relative labels and append the zone domain internally.
1816-1834: Extendactivity_codeenum with new DNS zone/record activitiesThe
Event.activity_codeenum doesn’t list the new DNS activities (dns.zone.create/update/delete,dns.zone.record.create/update/delete) you introduced inmanagement/server/activity/codes.go. As a result, documented values are out of sync with what the backend can emit.Consider appending these new codes to the enum so API clients see the full set of possible
activity_codevalues.management/internals/modules/zones/interface.go (1)
7-12: Zone Manager interface is fine; consider tighteningGetZonenamingThe CRUD interface here is consistent with the
Zonemodel and store APIs. For readability and consistency withDeleteZone(..., zoneID string), consider renaming the last parameter ofGetZonefromzonetozoneIDin implementations and usages.management/internals/modules/zones/zone.go (1)
60-77: Consider adding a maximum length check for the domain field.While
util.IsValidDomainvalidates the format, DNS domains have a maximum length of 253 characters. Consider adding a length check similar to the name field validation for defense in depth.if !util.IsValidDomain(z.Domain) { return errors.New("invalid zone domain format") } + + if len(z.Domain) > 253 { + return errors.New("zone domain exceeds maximum length of 253 characters") + }management/server/types/networkmapbuilder.go (1)
13-13: Normalize domains consistently and avoid dropping CustomZone fieldsThe new DNS zone filtering logic is sound, but two details are worth tightening up:
The comparison
customZone.Domain == dns.Fqdn(dnsDomain)will only match ifcustomZone.Domainis already in fully‑qualified form (trailing dot). IfZone.Domainis stored without a trailing dot, this will silently skip zones. Consider normalizing both sides (e.g.,dns.Fqdnon both or a shared helper) to avoid mismatches.When you rebuild a matching zone as
nbdns.CustomZone{Domain: customZone.Domain, Records: records}, any other fields onnbdns.CustomZoneare zeroed out. To future‑proof this, it’s safer to copy the existing struct and only overrideRecords(e.g.,z := customZone; z.Records = records; customZones[i] = z).Also applies to: 939-942, 978-982, 1028-1040
management/server/store/sql_store.go (1)
113-119: Zone/DNS record store methods mostly align; consider association scope and error detailThe new zone/DNS‑record CRUD methods look consistent with existing store patterns, but a couple of refinements would make them safer and easier to debug:
UpdateZone(and to a lesser extentUpdateDNSRecord) usesSelect("*").Save(zone). Givenzones.ZonedeclaresRecords []*records.Record \gorm:"foreignKey:ZoneID;references:ID"`inmanagement/internals/modules/zones/zone.golines 12–21, this can implicitly upsert associated records when callers only intend to update zone metadata. If records are supposed to be managed exclusively via the DNS record methods, it’s safer toOmit(clause.Associations)or explicitly select just the zone fields to avoid accidental writes toRecords`.The new log messages are quite generic (e.g.,
"failed to create to store"/"failed to delete dns record from store"). Including the relevant IDs (accountID,zoneID,recordID) in the log and/or wrapping error text will make production debugging a lot easier.These are behavioural/observability tweaks; the core implementation and account scoping look correct.
Also applies to: 4131-4294
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (31)
management/internals/controllers/network_map/controller/controller.go(13 hunks)management/internals/controllers/network_map/controller/repository.go(3 hunks)management/internals/modules/zones/interface.go(1 hunks)management/internals/modules/zones/manager/api.go(1 hunks)management/internals/modules/zones/manager/manager.go(1 hunks)management/internals/modules/zones/records/interface.go(1 hunks)management/internals/modules/zones/records/manager/api.go(1 hunks)management/internals/modules/zones/records/manager/manager.go(1 hunks)management/internals/modules/zones/records/record.go(1 hunks)management/internals/modules/zones/zone.go(1 hunks)management/internals/server/boot.go(1 hunks)management/internals/server/modules.go(2 hunks)management/internals/shared/grpc/conversion.go(1 hunks)management/server/account_test.go(1 hunks)management/server/activity/codes.go(2 hunks)management/server/http/handler.go(3 hunks)management/server/http/handlers/peers/peers_handler.go(2 hunks)management/server/http/testing/testing_tools/channel/channel.go(2 hunks)management/server/store/sql_store.go(3 hunks)management/server/store/store.go(2 hunks)management/server/types/account.go(2 hunks)management/server/types/networkmap.go(1 hunks)management/server/types/networkmap_golden_test.go(18 hunks)management/server/types/networkmapbuilder.go(5 hunks)management/server/util/util.go(2 hunks)shared/management/client/rest/client.go(2 hunks)shared/management/client/rest/dns_zones.go(1 hunks)shared/management/client/rest/dns_zones_test.go(1 hunks)shared/management/http/api/openapi.yml(3 hunks)shared/management/http/api/types.gen.go(4 hunks)shared/management/status/error.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
shared/management/client/rest/client.go (2)
shared/management/client/rest/dns.go (1)
DNSAPI(12-14)shared/management/client/rest/dns_zones.go (1)
DNSZonesAPI(12-14)
management/internals/modules/zones/records/interface.go (1)
management/internals/modules/zones/records/record.go (1)
Record(21-29)
management/internals/modules/zones/interface.go (2)
management/internals/modules/zones/zone.go (1)
Zone(13-22)shared/management/http/api/types.gen.go (1)
Zone(1910-1928)
management/server/http/handler.go (4)
management/internals/modules/zones/records/interface.go (1)
Manager(7-13)management/internals/modules/zones/interface.go (1)
Manager(7-13)management/internals/modules/zones/manager/api.go (1)
RegisterEndpoints(20-30)management/internals/modules/zones/records/manager/api.go (1)
RegisterEndpoints(20-30)
management/internals/shared/grpc/conversion.go (1)
shared/management/proto/management.pb.go (3)
SimpleRecord(2752-2762)SimpleRecord(2777-2777)SimpleRecord(2792-2794)
management/server/types/networkmap.go (2)
management/server/telemetry/accountmanager_metrics.go (1)
AccountManagerMetrics(11-17)management/server/types/networkmapbuilder.go (1)
NetworkMapCache(33-59)
management/internals/modules/zones/records/manager/manager.go (9)
management/server/store/store.go (1)
Store(52-222)management/internals/modules/zones/records/interface.go (1)
Manager(7-13)management/internals/modules/zones/manager/manager.go (1)
NewManager(23-29)management/internals/modules/zones/records/record.go (3)
Record(21-29)NewRecord(31-41)RecordTypeCNAME(18-18)management/server/permissions/modules/module.go (1)
Dns(11-11)management/server/permissions/operations/operation.go (2)
Read(7-7)Delete(9-9)shared/management/status/error.go (6)
NewPermissionValidationError(213-215)NewPermissionDeniedError(209-211)Type(46-46)Errorf(70-75)InvalidArgument(27-27)AlreadyExists(30-30)management/internals/modules/zones/zone.go (1)
Zone(13-22)route/route.go (1)
ID(48-48)
management/server/types/account.go (2)
dns/dns.go (1)
CustomZone(43-52)shared/management/proto/management.pb.go (3)
CustomZone(2680-2689)CustomZone(2704-2704)CustomZone(2719-2721)
management/internals/server/boot.go (1)
management/server/http/handler.go (1)
NewAPIHandler(57-145)
shared/management/client/rest/dns_zones_test.go (4)
shared/management/http/api/types.gen.go (8)
DNSRecord(418-433)DNSRecordTypeA(17-17)PostApiDnsZonesJSONRequestBody(2027-2027)PutApiDnsZonesZoneIdJSONRequestBody(2030-2030)PostApiDnsZonesZoneIdRecordsJSONRequestBody(2033-2033)PutApiDnsZonesZoneIdRecordsRecordIdJSONRequestBody(2036-2036)ZoneRequest(1931-1946)DNSRecordRequest(436-448)shared/management/http/util/util.go (1)
ErrorResponse(21-24)management/server/activity/codes.go (1)
Code(9-12)shared/management/status/error.go (1)
Error(54-57)
management/internals/controllers/network_map/controller/repository.go (1)
management/server/store/store.go (1)
LockingStrengthNone(49-49)
management/internals/modules/zones/records/record.go (2)
shared/management/http/api/types.gen.go (3)
DNSRecord(418-433)DNSRecordType(451-451)DNSRecordRequest(436-448)management/server/util/util.go (1)
IsValidDomain(58-63)
management/internals/controllers/network_map/controller/controller.go (4)
management/internals/controllers/network_map/interface.go (1)
Controller(23-39)management/server/types/account.go (1)
LookupMap(57-57)management/internals/modules/zones/records/record.go (3)
RecordTypeA(16-16)RecordTypeAAAA(17-17)RecordTypeCNAME(18-18)dns/dns.go (1)
DefaultClass(21-21)
management/server/store/store.go (3)
management/internals/modules/zones/zone.go (1)
Zone(13-22)shared/management/http/api/types.gen.go (1)
Zone(1910-1928)management/internals/modules/zones/records/record.go (1)
Record(21-29)
management/server/account_test.go (2)
dns/dns.go (1)
CustomZone(43-52)shared/management/proto/management.pb.go (3)
CustomZone(2680-2689)CustomZone(2704-2704)CustomZone(2719-2721)
management/server/store/sql_store.go (4)
management/internals/modules/zones/zone.go (1)
Zone(13-22)management/internals/modules/zones/records/record.go (1)
Record(21-29)shared/management/status/error.go (4)
Error(54-57)Errorf(70-75)NewZoneNotFoundError(257-259)NewDNSRecordNotFoundError(262-264)management/server/store/store.go (2)
LockingStrength(42-42)LockingStrengthNone(49-49)
management/internals/modules/zones/zone.go (3)
management/internals/modules/zones/records/record.go (1)
Record(21-29)shared/management/http/api/types.gen.go (2)
Zone(1910-1928)ZoneRequest(1931-1946)management/server/util/util.go (1)
IsValidDomain(58-63)
🔇 Additional comments (47)
management/internals/modules/zones/manager/api.go (1)
16-160: Zones HTTP handlers follow existing patterns cleanlyAuth extraction, validation, manager delegation, and JSON responses look consistent and correct; no issues spotted in routing or parameter handling.
management/internals/shared/grpc/conversion.go (1)
329-345: gRPC custom zone conversion correctly propagatesSearchDomainDisabledThe added
SearchDomainDisabledfield is wired through alongsideDomainand preallocatedRecords; conversion logic remains consistent.management/server/http/testing/testing_tools/channel/channel.go (1)
12-15: Test HTTP channel correctly wires zones and records managersUsing real
zonesManager.NewManagerandrecordsManager.NewManagerwith the test store and passing them intoNewAPIHandlerkeeps the black-box tests exercising the new DNS zones/records API surface as expected.Also applies to: 92-101
management/internals/server/modules.go (1)
9-12: Zones/records manager accessors align with existing server module patterns
ZonesManager()andRecordsManager()follow the same lazy-initialization pattern as other managers and use the expected dependencies (Store,AccountManager,PermissionsManager), so the wiring looks correct.Also applies to: 123-133
management/server/types/networkmap.go (1)
24-34: Network map API cleanly extended for multi‑zone DNSExtending
GetPeerNetworkMapExpwithdnsDomainandcustomZonesand forwarding them toNetworkMapCache.GetPeerNetworkMapis a minimal, coherent change; cache initialization semantics remain unchanged.shared/management/client/rest/client.go (1)
60-67: REST client DNSZones API wiring is consistent and completeAdding the
DNSZonesfield and initializing it ininitialize()mirrors the existingDNSAPI pattern and should make the new endpoints immediately usable via the client.Also applies to: 107-121
shared/management/status/error.go (1)
256-264: LGTM!The new error constructors follow the established pattern in this file and are consistent with other
NotFounderror types.management/server/http/handler.go (3)
17-20: LGTM!Import aliases are appropriately named to avoid conflicts with the interface types.
72-73: LGTM!New manager parameters follow the established injection pattern.
141-142: LGTM!Endpoint registration follows the established pattern and maintains logical grouping with DNS-related handlers.
management/internals/controllers/network_map/controller/repository.go (1)
6-6: LGTM!The new
GetAccountZonesmethod follows the established repository pattern, usingLockingStrengthNoneconsistent with other read methods in this file.Also applies to: 16-16, 42-45
shared/management/http/api/types.gen.go (1)
1-4: Generated code - no manual changes required.This file is auto-generated by
oapi-codegen. The new DNS zone and record types appear correctly structured with appropriate field types and optionality for request/response patterns.management/server/account_test.go (1)
397-399: Test correctly adapts to updatedGetPeerNetworkMapsignature with domain and zones slice parameters.The test at lines 397-399 properly implements the new signature:
- Passes
"netbird.io"as thednsDomainstring parameter- Wraps
customZonein[]nbdns.CustomZone{customZone}slice to support multiple zones- All other parameters (context, peerID, validatedPeers, policies, routers, metrics) match the function signature
The implementation aligns with the underlying function that iterates over the zones slice to filter records per domain.
management/internals/server/boot.go (1)
96-96: Perfect! All verifications are complete. The signature shows:
- Parameter 15:
zManager zones.Manager- Parameter 16:
rManager records.Manager- Parameter 17:
networkMapController network_map.ControllerThese match the
ZonesManager()andRecordsManager()methods that returnzones.Managerandrecords.Managerrespectively. The code change is correctly implemented.
LGTM!
Both
ZonesManager()andRecordsManager()methods exist onBaseServerand return the correct types (zones.Managerandrecords.Manager). TheNewAPIHandlersignature correctly accepts these parameters at positions 15-16, and they are properly passed from the call site.management/server/activity/codes.go (1)
183-189: DNS zone/record activity codes and mappings look consistentNew DNS activity constants and their
activityMapentries follow the existing naming and coding conventions; no functional issues spotted here.Also applies to: 300-306
management/internals/modules/zones/records/interface.go (1)
7-13: Records Manager interface shape is reasonableThe CRUD interface is coherent (context-first, explicit account/user/zone IDs) and matches the underlying
Recordmodel and store APIs.management/server/types/account.go (1)
1755-1778: Per‑peer DNS record filtering helper looks correct
filterZoneRecordsForPeersbuilds a set of IPs for the target peer, its reachable peers, and expired peers, and filters zone records byRDatamembership. The logic is straightforward and matches the goal of scoping DNS responses to the peer’s connectivity set.shared/management/http/api/openapi.yml (1)
1710-1756: Zone schemas look consistent with generated types
ZoneRequest/Zoneschemas (fields, required sets, and examples) line up with the generatedapi.Zone/api.ZoneRequeststructs and the domain model (zones.Zone). The use ofEnabled *boolin the request and requiredenabledin the response is a sensible pattern for create/update semantics.management/internals/modules/zones/records/manager/manager.go (1)
33-55: Permissions + transactional flow for DNS records look solid
- Permission checks for read/create/update/delete use the DNS module and appropriate operations and map errors via
status.NewPermissionValidationError/NewPermissionDeniedError.- All mutating operations run inside
ExecuteInTransaction, lock the zone (LockingStrengthUpdate), update the record, and increment the network serial before emitting events.This pattern matches the rest of the codebase’s account/DNS workflows.
Also applies to: 101-205
shared/management/client/rest/dns_zones_test.go (3)
40-213: Mocked DNS zones client tests provide good coverageThe mocked tests for zone CRUD operations verify HTTP methods, request body shapes (via unmarshalling), and error handling for 4xx responses. This should catch most regressions in the REST client layer.
215-390: Mocked DNS record client tests are comprehensiveSimilarly, record CRUD tests validate correct endpoints, methods, and JSON payloads, plus propagate server error responses into Go errors, which is exactly what the client should be doing.
392-460: End‑to‑end integration test nicely validates the full DNS zones/records flow
TestDNSZones_Integrationwalks through create/list/get/update/delete for both zones and records against a black‑box server, asserting realistic values (full FQDN for record name, distribution groups, etc.). This is a valuable high‑level guardrail for the new feature.management/internals/modules/zones/records/manager/api.go (6)
16-30: LGTM!The handler struct and route registration are well-structured. The RESTful endpoints follow proper conventions for DNS record CRUD operations.
32-57: LGTM!The
getAllRecordshandler correctly validates authentication, zone ID, and properly converts internal records to API responses.
59-93: LGTM!The
createRecordhandler follows the standard pattern: decode request, convert to domain model, validate, and delegate to manager. The error handling is consistent.
95-121: LGTM!The
getRecordhandler correctly validates both zone and record IDs before delegating to the manager.
123-164: LGTM!The
updateRecordhandler correctly uses the path parameter for the record ID, ensuring the URL is the source of truth for the resource identifier.
166-191: LGTM!The
deleteRecordhandler follows proper REST conventions, returning an empty object on successful deletion.management/internals/modules/zones/zone.go (5)
13-22: LGTM!The
Zonestruct is well-defined with appropriate GORM tags. The JSON serializer forDistributionGroupsand the foreign key relationship forRecordsare correctly configured.
24-34: LGTM!The constructor properly initializes all fields and generates a unique ID using
xid.
36-45: LGTM!The API response mapping is complete and correctly maps all Zone fields to the API representation.
47-58: LGTM!The
FromAPIRequestmethod correctly handles the optionalEnabledfield by defaulting totruewhen not provided, which is a sensible default for new zones.
79-81: LGTM!The
EventMetamethod provides appropriate metadata for event tracking.management/internals/controllers/network_map/controller/controller.go (6)
174-178: LGTM!Proper error handling for zone retrieval with logging and early return on failure.
831-888: LGTM!The
filterPeerAppliedZonesfunction correctly:
- Filters zones by peer group membership
- Skips disabled zones and zones without records
- Maps record types to DNS type constants
- Logs warnings for unknown record types instead of failing
- Properly inverts
EnableSearchDomaintoSearchDomainDisabled
320-328: LGTM!The zone retrieval and filtering logic in
UpdateAccountPeeris consistent with the pattern used in other methods.
459-470: LGTM!Zone retrieval and filtering follows the established pattern in the controller.
789-800: LGTM!The zone handling in
GetNetworkMapis consistent with other controller methods.
500-518: LGTM!The signature update to accept
customZones []nbdns.CustomZoneproperly supports the multi-zone architecture.management/server/types/networkmap_golden_test.go (2)
107-108: LGTM!The test signature updates are consistent across all test functions and benchmarks.
72-72: Golden tests intentionally use empty zones; CustomZone functionality is tested separately.The golden tests pass empty
[]dns.CustomZone{}by design—they validate baseline network map structure rather than DNS zone filtering. Comprehensive CustomZone testing already exists inmanagement/server/account_test.go(line 398), whereGetPeersCustomZone()is called with actual zone data, and inmanagement/server/types/account_test.go(lines 1119–1209), which covers zone record filtering scenarios. No additional coverage is needed in these golden tests.management/internals/modules/zones/records/record.go (5)
13-29: LGTM!The
RecordTypeconstants andRecordstruct are well-defined with appropriate GORM tags for indexing and primary key.
31-41: LGTM!The constructor properly initializes all fields and generates a unique ID.
43-59: LGTM!The API conversion methods correctly map between internal and external record representations.
61-96: LGTM!The validation logic is comprehensive:
- Name format validation using domain regex
- Type-specific content validation (IPv4, IPv6, domain)
- Non-negative TTL check
- Clear error messages for each validation failure
98-107: LGTM!The
EventMetamethod appropriately includes both record details and zone context for comprehensive event tracking.management/server/store/store.go (1)
26-28: DNS zone and DNS record methods on Store look consistentThe new zone and DNS record operations on
Storefollow existing patterns (account‑scoped IDs, read methods withLockingStrength) and match theSqlStoreimplementations. No issues from the interface design side.Also applies to: 209-221
management/internals/controllers/network_map/controller/controller.go
Outdated
Show resolved
Hide resolved
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
management/internals/modules/zones/manager/manager.go (1)
167-172: Fix closure capture of loop variablerecord.This issue was flagged in a previous review and remains unfixed. The closures built inside the loop capture
recordby reference, so when they execute (lines 184-186), all of them will reference the last record from the loop. This causes incorrect event IDs and metadata for all deleted records except the last one.Apply this diff to capture the current record value in each iteration:
- for _, record := range records { - eventsToStore = append(eventsToStore, func() { - meta := record.EventMeta(zone.ID, zone.Name) - m.accountManager.StoreEvent(ctx, userID, record.ID, accountID, activity.DNSRecordDeleted, meta) - }) - } + for _, record := range records { + rec := record + eventsToStore = append(eventsToStore, func() { + meta := rec.EventMeta(zone.ID, zone.Name) + m.accountManager.StoreEvent(ctx, userID, rec.ID, accountID, activity.DNSRecordDeleted, meta) + }) + }
🧹 Nitpick comments (2)
management/server/store/sql_store_test.go (1)
3859-3879: Consider testing cascading delete behavior.While the current tests verify zone deletion works correctly, it would be helpful to optionally add a test that verifies whether DNS records are automatically deleted when their parent zone is deleted (if cascading delete is implemented), or if they need to be deleted separately.
Example test structure:
func TestSqlStore_DeleteZoneCascade(t *testing.T) { // Create zone // Create DNS records in zone // Delete zone // Verify records are also deleted (or handle accordingly) }management/internals/modules/zones/manager/manager.go (1)
188-188: Consider using a detached context for background peer updates.The goroutine receives the request context
ctx, which may be canceled when the HTTP request completes. IfUpdateAccountPeersis a long-running operation, it could be interrupted prematurely.If peer updates should complete regardless of client disconnection, consider using a detached context:
go m.accountManager.UpdateAccountPeers(context.Background(), accountID)However, if cancellation is intentional (e.g., to respect request timeouts), the current implementation is fine.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
management/internals/controllers/network_map/controller/controller.go(13 hunks)management/internals/controllers/network_map/controller/controller_test.go(2 hunks)management/internals/modules/zones/manager/manager.go(1 hunks)management/internals/modules/zones/records/manager/manager.go(1 hunks)management/internals/modules/zones/records/record.go(1 hunks)management/server/store/sql_store_test.go(2 hunks)shared/management/http/api/openapi.yml(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
management/internals/modules/zones/records/record.go (2)
shared/management/http/api/types.gen.go (3)
DNSRecord(418-433)DNSRecordType(451-451)DNSRecordRequest(436-448)management/server/util/util.go (1)
IsValidDomain(58-63)
management/internals/modules/zones/records/manager/manager.go (8)
management/server/store/store.go (1)
Store(52-222)management/internals/modules/zones/records/interface.go (1)
Manager(7-13)management/internals/modules/zones/manager/manager.go (1)
NewManager(23-29)management/internals/modules/zones/records/record.go (3)
Record(21-29)NewRecord(31-41)RecordTypeCNAME(18-18)management/server/permissions/modules/module.go (1)
Dns(11-11)management/server/permissions/operations/operation.go (2)
Read(7-7)Delete(9-9)shared/management/status/error.go (4)
NewPermissionValidationError(213-215)NewPermissionDeniedError(209-211)Type(46-46)Errorf(70-75)management/internals/modules/zones/zone.go (1)
Zone(13-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: release_ui_darwin
- GitHub Check: release
- GitHub Check: release_ui
- GitHub Check: Linux
- GitHub Check: Client / Unit
- GitHub Check: Darwin
- GitHub Check: Windows
- GitHub Check: Build Cache
- GitHub Check: Client / Unit
- GitHub Check: Android / Build
- GitHub Check: iOS / Build
- GitHub Check: Client / Unit
- GitHub Check: JS / Lint
🔇 Additional comments (33)
management/internals/modules/zones/records/manager/manager.go (6)
19-31: LGTM!The manager implementation follows the established pattern with proper dependency injection of store, account manager, and permissions manager.
33-55: LGTM!Read operations properly implement permission checks and use appropriate locking strength for concurrent reads.
57-101: LGTM!The previous concern about missing peer updates has been resolved. The method now correctly triggers
UpdateAccountPeersat line 98, ensuring new DNS records are propagated to peers proactively, consistent with the update and delete paths.
103-160: LGTM!The update logic correctly distinguishes between changes that require conflict revalidation (Name, Type, Content) and those that don't (TTL). The implementation properly updates all fields while only triggering expensive conflict checks when necessary.
162-207: LGTM!Delete operation correctly implemented with proper locking, transactional safety, event logging, and peer updates.
209-236: LGTM!The zone membership validation has been corrected to properly check label boundaries (line 211), addressing the previous review concern. The implementation now correctly accepts only
example.comor*.example.comfor zoneexample.com, while rejectingnotexample.com.The duplicate and CNAME conflict checks are also correctly implemented.
shared/management/http/api/openapi.yml (3)
1710-1801: LGTM!The previous issue with
DNSRecordrequiring an undefinedenabledfield has been resolved. The schema now correctly requires onlyid(line 1800) and inherits the necessary fields fromDNSRecordRequest. All DNS Zone and Record schemas are well-defined and consistent with the backend implementation.
4592-4741: LGTM!All DNS Zone CRUD endpoints are properly defined with:
- Appropriate HTTP methods and paths
- Correct security requirements
- Proper request/response schemas
- Comprehensive error response codes
The API design follows RESTful conventions consistently.
4742-4932: LGTM!DNS Record endpoints are well-structured as nested resources under zones (
/api/dns/zones/{zoneId}/records). All endpoints properly:
- Define required path parameters (zoneId, recordId)
- Use correct request/response schemas
- Include appropriate error codes (notably 404 for resource-not-found scenarios)
- Follow consistent patterns with the parent zone endpoints
management/server/store/sql_store_test.go (3)
25-26: LGTM! New imports are correctly added for zone and DNS record testing.The imports properly reference the new internal modules for zones and DNS records that align with the test coverage being added.
3723-3879: Excellent test coverage for zone CRUD operations.The zone tests comprehensively cover creation, retrieval, update, and deletion with proper error handling and NotFound verification after deletion. The test structure follows existing patterns in the file and uses appropriate assertions.
3881-4126: Comprehensive DNS record test coverage with excellent structure.The DNS record tests thoroughly cover CRUD operations including:
- Creation with proper zone dependencies
- Retrieval by ID and by name
- Updates to all record fields
- Individual and bulk deletion
- Multiple record types (A, AAAA, CNAME)
All tests include proper error handling and NotFound verification.
management/internals/modules/zones/records/record.go (6)
13-29: Well-structured Record model with proper GORM configuration.The RecordType constants provide type safety for the three supported DNS record types, and the Record struct includes appropriate GORM tags for database indexing and primary key designation.
31-41: LGTM! Constructor properly initializes Record with unique ID.The use of
xidfor ID generation ensures uniqueness, and all fields are properly initialized.
43-59: API conversion methods correctly map between internal and external types.Both
ToAPIResponseandFromAPIRequestproperly convert between the internal Record model and the API types, with correct field mappings.
61-96: Comprehensive validation with appropriate type-specific checks.The validation logic properly enforces:
- Non-empty name with domain format validation
- Type-specific content validation (IPv4 for A, IPv6 for AAAA, domain for CNAME)
- Non-negative TTL
Note: The TTL validation allows 0, which is valid in DNS for indicating no caching should occur.
120-129: IPv6 validation fix has been correctly applied.The
validateIPv6function now properly rejects IPv4 addresses by checkingip.To4() != nil. This correctly addresses the critical bug from the previous review whereip.To16() == nilwould incorrectly accept IPv4 addresses (sinceTo16()returns a 16-byte IPv4-mapped representation for IPv4 addresses).The current implementation ensures only true IPv6 addresses are accepted for AAAA records.
98-118: EventMeta and validateIPv4 are correctly implemented.The
EventMetamethod provides comprehensive metadata for event tracking, andvalidateIPv4properly validates IPv4 addresses using the correctip.To4() == nilcheck.management/internals/controllers/network_map/controller/controller_test.go (2)
4-17: LGTM!The new imports are appropriate for the test function and support comprehensive DNS zone testing.
120-630: Excellent test coverage!The table-driven test comprehensively validates
filterPeerAppliedZonesacross multiple scenarios:
- Access control via distribution groups
- Zone enablement filtering
- Empty record handling
- Multiple record types (A, AAAA, CNAME)
- Search domain configuration
- Multi-zone and multi-group scenarios
The assertions thoroughly verify both zone-level properties and individual record details.
management/internals/controllers/network_map/controller/controller.go (12)
15-15: LGTM!The new imports support DNS zone filtering and record type mapping in the
filterPeerAppliedZonesfunction.Also applies to: 23-24
174-178: LGTM!Proper error handling for account zones retrieval with logging and early return.
203-205: LGTM! Bug from previous review has been fixed.The code correctly uses
p.ID(goroutine parameter) instead of the loop variable, avoiding the closure capture issue flagged in the previous review. The customZones construction properly filters zones by peer group membership and appends the peers custom zone.
210-213: LGTM!Both experimental and non-experimental network map generation paths correctly updated to accept the
customZonesslice parameter.
320-327: LGTM!Consistent zone retrieval and filtering pattern applied correctly.
332-335: LGTM!Function calls correctly updated to pass
customZonesparameter in both code paths.
459-469: LGTM!Proper sequence: retrieve account zones, get DNS domain and peers custom zone, then filter zones based on peer groups.
480-483: LGTM!Function calls consistently updated with
customZonesparameter.
500-518: LGTM!The
getPeerNetworkMapExpfunction signature correctly updated to acceptcustomZonesslice, and properly retrieves DNS domain from account settings before passing to the network map builder.
790-799: LGTM!Consistent zone retrieval and filtering pattern properly implemented in
GetNetworkMap.
810-813: LGTM!Network map generation calls correctly updated with
customZonesparameter.
831-888: Well-implemented helper function!The
filterPeerAppliedZonesfunction correctly:
- Filters zones based on peer group access control
- Skips disabled zones and zones without records
- Maps DNS record types (A, AAAA, CNAME) with proper fallback for unknown types
- Constructs properly formatted FQDNs using
dns.Fqdn()- Inverts
EnableSearchDomaintoSearchDomainDisabledcorrectlyThe early return optimization and access control logic are both sound.
management/internals/modules/zones/manager/manager.go (1)
88-129: Verify if peer updates and serial increment are needed after zone updates.
UpdateZonecorrectly handles the transaction error (lines 122-124, resolving the previous review concern), but likeCreateZone, it does not callIncrementNetworkSerialorUpdateAccountPeers. Changes to zone properties (enabled status, distribution groups, domain, search domain settings) likely affect the network map and should trigger peer updates.Use the verification script from the
CreateZonecomment above to check if Update operations typically call these methods.
…ne management Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
Signed-off-by: bcmmbaga <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
shared/management/http/api/openapi.yml (3)
4594-4743: /api/dns/zones collection and item endpoints match existing REST patterns
- List/create (
GET/POST) endpoints for zones reuse the standard security blocks and response code conventions (200 + 400/401/403/500 on write).- Single-zone
GET/PUT/DELETEcorrectly usezoneIdas a path param with type string and add404for missing zones, which is actually stricter than some of the older DNS endpoints.One minor consistency nit: existing
DELETEendpoints often include an explicit emptycontent: { }block on 200, whereasDELETE /api/dns/zones/{zoneId}omitscontent. Both are valid OpenAPI, but you may want to align with the existing deletes for uniformity.
4744-4814: /api/dns/zones/{zoneId}/records collection endpoints are well-shaped
GETreturnsDNSRecord[]and includes404when the parent zone is missing.POSTacceptsDNSRecordRequestand returns a fullDNSRecord, consistent with other create endpoints returning the created resource.Again, optional nit: you don’t expose a
400for theGETdespite other list endpoints sometimes defining it even without query params; that’s fine semantically, but if you aim for strict uniformity you might consider adding it later.
4816-4934: Record-level endpoints look correct; consider aligning delete response shape
GET/PUTfor/api/dns/zones/{zoneId}/records/{recordId}use bothzoneIdandrecordIdwith clear descriptions and return a singleDNSRecord, including404for not found.PUTreusesDNSRecordRequestfor the body, which matches the Go types and keeps id server-controlled.For
DELETE, you return a bare 200 with description only, whereas most existing deletes specifycontent: { }. It’s valid as-is, but you might want to add the emptycontentblock for consistency with the rest of the API surface.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
management/internals/modules/zones/zone.go(1 hunks)management/server/store/sql_store.go(3 hunks)shared/management/http/api/openapi.yml(3 hunks)shared/management/http/api/types.gen.go(4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
📚 Learning: 2025-11-28T12:20:47.254Z
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
Applied to files:
shared/management/http/api/openapi.ymlshared/management/http/api/types.gen.gomanagement/server/store/sql_store.gomanagement/internals/modules/zones/zone.go
🧬 Code graph analysis (1)
management/internals/modules/zones/zone.go (3)
management/internals/modules/zones/records/record.go (1)
Record(21-29)shared/management/http/api/types.gen.go (3)
Zone(1910-1931)DNSRecord(418-433)ZoneRequest(1934-1949)management/server/util/util.go (1)
IsValidDomain(58-63)
🔇 Additional comments (12)
management/internals/modules/zones/zone.go (1)
13-89: LGTM! Clean zone model implementation.The Zone type is well-structured with appropriate GORM tags, proper foreign key relationships, sensible validation rules, and clean API conversion logic. The default handling for the optional
Enabledfield (defaulting totruewhennil) aligns with typical feature flag patterns.management/server/store/sql_store.go (4)
30-31: LGTM! Proper integration into auto-migration.The imports and auto-migration additions correctly integrate the new zones and records models into the store initialization, ensuring database schema readiness.
Also applies to: 118-118
4141-4149: LGTM!The use of
Select("*").Save()for full updates is consistent with other entity update patterns in this store (e.g.,SavePolicy).
4151-4199: LGTM! Standard CRUD patterns.The zone retrieval and deletion methods follow established store conventions with proper locking support, association preloading, and error handling.
4235-4295: LGTM! DNS record retrieval methods are properly scoped.The
GetDNSRecordByID,GetZoneDNSRecords,GetZoneDNSRecordsByName, andDeleteZoneDNSRecordsmethods all correctly scope operations by bothaccountIDandzoneID, ensuring proper data isolation.shared/management/http/api/types.gen.go (5)
15-20: DNSRecordType constants correctly mirror OpenAPI enumThe A/AAAA/CNAME constants align with the
DNSRecordTypestring enum in the OpenAPI spec; nothing to change here.
417-452: DNSRecord / DNSRecordRequest structs match schema expectations
DNSRecordRequestandDNSRecordfields (name, type, content, ttl, id) line up with the OpenAPI definitions and required sets; using a separate request type withoutidis consistent with other resources in this API.
450-451: String alias for DNSRecordType is appropriateDefining
DNSRecordTypeasstringkeeps the generated client simple while still being constrained by the enum constants above; this matches existing enum patterns in this file.
1909-1949: Zone / ZoneRequest shapes are consistent but rely on backend defaults forenabled
ZoneRequest.Enabledas*bool(omittable) vsZone.Enabledasboolmatches the OpenAPI idea ofenabledbeing optional on write with a default oftrue, and required on read.- This puts the onus on handlers to interpret
nilas “use default” and not asfalse.Please double-check the zone create/update handlers treat
nilEnabledcorrectly (defaulting totrueon create and/or leaving it unchanged on update) so behavior matches the OpenAPI default.
2029-2040: Request body aliases for DNS zones/records follow existing patternsThe new
PostApiDnsZones*/PutApiDnsZones*and records request-body aliases correctly point toZoneRequest/DNSRecordRequest, matching the OpenAPI requestBody schemas and the conventions used elsewhere in this generated file.shared/management/http/api/openapi.yml (2)
28-29: New “DNS Zones” tag integrates cleanly into tag listTag naming and description match the style of existing tags and will group the new endpoints coherently in generated docs.
1710-1803: Zone/DNSRecord schemas are structurally sound and consistent**
ZoneRequestandZonefollow the existing pattern of *Request vs full resource (allOfwith anid-carrying object), similar to networks/resources/routes.enabledbeing optional onZoneRequest(with a default) and required viaZone’srequiredarray is a validallOfusage and matches the generated Go types.DNSRecordTypeenum andDNSRecordRequest/DNSRecordshapes line up with the Go types (A/AAAA/CNAME;name,type,content,ttl, plusidon the read model).No inconsistencies spotted between these schemas and the generated
apipackage.
# Conflicts: # management/internals/controllers/network_map/controller/controller.go # management/internals/controllers/network_map/controller/repository.go # management/internals/server/modules.go # management/server/activity/codes.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
management/server/account_test.go (1)
399-401: Avoid hard‑coded DNS domain in test; useaccount.DomainThe new
GetPeerNetworkMapsignature usage looks correct, but you can tighten the test by deriving the domain from the account instead of repeating the literal:- customZone := account.GetPeersCustomZone(context.Background(), "netbird.io") - networkMap := account.GetPeerNetworkMap(context.Background(), testCase.peerID, "netbird.io", []nbdns.CustomZone{customZone}, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil) + customZone := account.GetPeersCustomZone(context.Background(), account.Domain) + networkMap := account.GetPeerNetworkMap(context.Background(), testCase.peerID, account.Domain, []nbdns.CustomZone{customZone}, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)shared/management/http/api/openapi.yml (1)
4604-4787: Add400responses to DNS Zones list endpoints for consistencyMost list endpoints in this spec expose a
400response (e.g./api/dns/nameservers,/api/peers, etc.), but:
GET /api/dns/zonesGET /api/dns/zones/{zoneId}/recordscurrently only define
200, auth, and500(plus404on the records list).For consistency with the rest of the API surface and to accommodate potential client or path validation errors, consider adding
400to both:/api/dns/zones: get: @@ responses: '200': description: A JSON Array of DNS Zones content: application/json: schema: type: array items: $ref: '#/components/schemas/Zone' + '400': + "$ref": "#/components/responses/bad_request" '401': "$ref": "#/components/responses/requires_authentication" @@ /api/dns/zones/{zoneId}/records: get: @@ responses: '200': description: A JSON Array of DNS Records content: application/json: schema: type: array items: $ref: '#/components/schemas/DNSRecord' + '400': + "$ref": "#/components/responses/bad_request" '401': "$ref": "#/components/responses/requires_authentication"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
management/internals/controllers/network_map/controller/controller.go(14 hunks)management/internals/controllers/network_map/controller/repository.go(3 hunks)management/internals/server/boot.go(1 hunks)management/internals/server/modules.go(2 hunks)management/server/account_test.go(1 hunks)management/server/activity/codes.go(2 hunks)management/server/http/handler.go(3 hunks)management/server/http/handlers/peers/peers_handler.go(2 hunks)management/server/http/testing/testing_tools/channel/channel.go(2 hunks)shared/management/http/api/openapi.yml(3 hunks)shared/management/http/api/types.gen.go(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- management/server/http/handlers/peers/peers_handler.go
- management/server/http/testing/testing_tools/channel/channel.go
- management/server/http/handler.go
- management/internals/server/boot.go
- management/internals/controllers/network_map/controller/repository.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
📚 Learning: 2025-11-28T12:20:47.254Z
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
Applied to files:
management/internals/server/modules.goshared/management/http/api/openapi.ymlmanagement/internals/controllers/network_map/controller/controller.goshared/management/http/api/types.gen.gomanagement/server/account_test.go
🧬 Code graph analysis (2)
management/internals/server/modules.go (5)
management/internals/modules/zones/records/interface.go (1)
Manager(7-13)management/internals/modules/zones/interface.go (1)
Manager(7-13)management/internals/server/container.go (1)
Create(6-10)management/internals/modules/zones/manager/manager.go (1)
NewManager(23-29)management/internals/modules/zones/records/manager/manager.go (1)
NewManager(25-31)
management/server/account_test.go (2)
dns/dns.go (1)
CustomZone(43-52)shared/management/proto/management.pb.go (3)
CustomZone(2680-2689)CustomZone(2704-2704)CustomZone(2719-2721)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
- GitHub Check: Client / Unit (386)
- GitHub Check: Management / Unit (amd64, sqlite)
- GitHub Check: Relay / Unit (386)
- GitHub Check: Management / Unit (amd64, mysql)
- GitHub Check: Management / Unit (amd64, postgres)
- GitHub Check: Management / Benchmark (API) (amd64, postgres)
- GitHub Check: Management / Benchmark (API) (amd64, sqlite)
- GitHub Check: Relay / Unit (amd64, -race)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Management / Integration (amd64, postgres)
- GitHub Check: Management / Benchmark (amd64, postgres)
- GitHub Check: Signal / Unit (amd64)
- GitHub Check: Signal / Unit (386)
- GitHub Check: Management / Benchmark (amd64, sqlite)
- GitHub Check: Android / Build
- GitHub Check: Linux
- GitHub Check: Windows
- GitHub Check: iOS / Build
- GitHub Check: Darwin
- GitHub Check: release_ui_darwin
- GitHub Check: Client / Unit
- GitHub Check: release_ui
- GitHub Check: Client / Unit
- GitHub Check: JS / Lint
- GitHub Check: Client / Unit
- GitHub Check: release
🔇 Additional comments (14)
management/internals/server/modules.go (1)
10-13: Zones/records manager wiring is consistent and correctThe new
ZonesManagerandRecordsManageraccessors follow the existing lazyCreatepattern, inject the expected dependencies (store, account, permissions), and mirrorNetworksManager/ResourcesManagerstyle. No changes needed.Also applies to: 126-136
shared/management/http/api/openapi.yml (1)
28-29: DNS Zones tagging and schemas look coherentThe new
DNS Zonestag plusZoneRequest/Zoneand DNS record schemas are internally consistent and match existing patterns:
Zonecorrectly composesZoneRequestviaallOf, soenabledis defined where it’s required.ZoneRequestvsZoneseparation (optionalenabledon request, required on response) is a nice touch.DNSRecordType/DNSRecordRequest/DNSRecordcleanly model the limited A/AAAA/CNAME surface.No changes needed here.
Also applies to: 1710-1803
shared/management/http/api/types.gen.go (4)
15-20: LGTM: DNS record type constants and enum definition.The DNS record type constants cover the essential record types (A, AAAA, CNAME) for custom DNS zones. The string-based enum follows the existing pattern in this generated file.
Also applies to: 450-451
417-448: LGTM: DNS record models follow REST conventions.The separation between
DNSRecord(with ID) andDNSRecordRequest(without ID) follows standard RESTful API patterns. The field types are appropriate for DNS record data.
1909-1949: LGTM: Zone models are well-structured.The
ZoneandZoneRequeststructures provide comprehensive fields for DNS zone management. The use ofDistributionGroupsfor access control aligns with NetBird's group-based architecture, and embeddingRecordsin the zone response is good for API ergonomics.
2035-2045: LGTM: Request body type aliases are consistent.The request body type aliases follow the established oapi-codegen pattern and cover all necessary CRUD operations for DNS zones and records.
management/internals/controllers/network_map/controller/controller.go (7)
15-15: LGTM: New imports support DNS zones feature.The addition of
miekg/dns(standard Go DNS library) and the internal zones/records modules are necessary for the multi-zone DNS support being added.Also applies to: 24-25
202-206: LGTM: Comprehensive error handling and clean zone filtering pattern.The account zones retrieval includes proper error logging and propagation. The pattern of filtering zones by peer groups and then appending the peers custom zone is clear and correctly uses the goroutine parameter
p.Also applies to: 231-233, 238-238, 240-240
348-356: LGTM: Consistent zone handling pattern in UpdateAccountPeer.The zone retrieval and filtering logic matches the pattern in
sendUpdateAccountPeers, maintaining consistency across the codebase.Also applies to: 360-360, 362-362
455-465: LGTM: Zone handling consistent in GetValidatedPeerWithMap.The zone filtering follows the established pattern with appropriate error handling.
Also applies to: 476-476, 478-478
501-501: LGTM: Function signature updated for multi-zone support.The change from a single custom zone to a slice of custom zones enables multiple DNS zones per account. Extracting
dnsDomainfrom account settings before passing to the network map builder is appropriate.Also applies to: 512-513
834-843: LGTM: Zone handling in GetNetworkMap follows the pattern.The zone retrieval and filtering logic is consistent with the other updated functions.
Also applies to: 854-854, 856-856
871-928: LGTM: Well-structured zone filtering implementation.The
filterPeerAppliedZonesfunction correctly implements multi-zone DNS filtering:
- Early return for peers without groups is efficient
- Filtering logic correctly checks zone enabled status and non-empty records (consistent with the learning that zones without records don't affect network maps)
- Distribution group access check properly evaluates peer membership
- DNS record type mapping covers the standard types (A, AAAA, CNAME) with appropriate warning logging for unknown types
- Use of
dns.Fqdn()ensures proper DNS format for names and domains- The function is defensive and handles edge cases appropriately
Based on learnings, zones without records are correctly filtered out.
management/server/activity/codes.go (1)
184-190: LGTM: DNS activity codes follow established patterns.The six new activity constants (DNS zone and record create/update/delete) use sequential values and follow the existing naming conventions. The activity map entries have clear, descriptive messages and use hierarchical code strings (
dns.zone.*anddns.zone.record.*) consistent with other activity codes in the system.Also applies to: 302-308
Signed-off-by: bcmmbaga <[email protected]>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
management/server/store/sql_store.go (1)
4141-4149: Consider omitting associations in UpdateZone.
UpdateZoneusesSelect("*").Save(zone)without explicitly omitting associations. If a zone is retrieved viaGetZoneByID(which preloadsRecords), then modified and saved, GORM might attempt to update the nested records as well. For consistency with similar operations (e.g.,UpdateGroupat line 3163) and to prevent unintended association updates, consider usingOmit(clause.Associations):func (s *SqlStore) UpdateZone(ctx context.Context, zone *zones.Zone) error { - result := s.db.Select("*").Save(zone) + result := s.db.Omit(clause.Associations).Save(zone) if result.Error != nil { log.WithContext(ctx).Errorf("failed to update zone to store: %v", result.Error) return status.Errorf(status.Internal, "failed to update zone to store")This ensures that records are only modified through their dedicated CRUD operations (
CreateDNSRecord,UpdateDNSRecord, etc.).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
management/internals/modules/zones/records/manager/manager.go(1 hunks)management/server/store/sql_store.go(3 hunks)management/server/store/sql_store_test.go(2 hunks)management/server/store/store.go(2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
📚 Learning: 2025-11-28T12:20:47.254Z
Learnt from: bcmmbaga
Repo: netbirdio/netbird PR: 4849
File: management/internals/modules/zones/manager/manager.go:55-86
Timestamp: 2025-11-28T12:20:47.254Z
Learning: In the NetBird management server, DNS zones without records are automatically filtered out in network map generation (filterPeerAppliedZones in management/internals/controllers/network_map/controller/controller.go checks `len(zone.Records) == 0`). Therefore, CreateZone operations don't need to call UpdateAccountPeers since empty zones don't affect the network map.
Applied to files:
management/server/store/sql_store_test.gomanagement/server/store/store.gomanagement/server/store/sql_store.gomanagement/internals/modules/zones/records/manager/manager.go
🧬 Code graph analysis (2)
management/server/store/store.go (3)
management/internals/modules/zones/zone.go (1)
Zone(13-22)shared/management/http/api/types.gen.go (1)
Zone(1910-1931)management/internals/modules/zones/records/record.go (1)
Record(21-29)
management/server/store/sql_store.go (4)
management/internals/modules/zones/zone.go (1)
Zone(13-22)management/internals/modules/zones/records/record.go (1)
Record(21-29)shared/management/status/error.go (4)
Error(54-57)Errorf(70-75)NewZoneNotFoundError(257-259)NewDNSRecordNotFoundError(262-264)management/server/store/store.go (2)
LockingStrength(42-42)LockingStrengthNone(49-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
- GitHub Check: Management / Benchmark (API) (amd64, sqlite)
- GitHub Check: Management / Unit (amd64, sqlite)
- GitHub Check: Management / Integration (amd64, sqlite)
- GitHub Check: Management / Unit (amd64, postgres)
- GitHub Check: Management / Integration (amd64, postgres)
- GitHub Check: Management / Unit (amd64, mysql)
- GitHub Check: Management / Benchmark (API) (amd64, postgres)
- GitHub Check: Management / Benchmark (amd64, sqlite)
- GitHub Check: Relay / Unit (386)
- GitHub Check: Management / Benchmark (amd64, postgres)
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Relay / Unit (amd64, -race)
- GitHub Check: Signal / Unit (amd64)
- GitHub Check: Client / Unit (386)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: release
- GitHub Check: release_ui_darwin
- GitHub Check: iOS / Build
- GitHub Check: Client / Unit
- GitHub Check: Client / Unit
- GitHub Check: Android / Build
- GitHub Check: Windows
- GitHub Check: Darwin
- GitHub Check: Linux
- GitHub Check: JS / Lint
- GitHub Check: Client / Unit
🔇 Additional comments (16)
management/internals/modules/zones/records/manager/manager.go (6)
19-31: LGTM!Clean constructor and dependency injection pattern for the records manager.
33-55: LGTM!Read operations correctly validate permissions and use appropriate locking strength for queries.
57-101: LGTM!The CreateRecord implementation correctly:
- Validates permissions before mutation
- Uses transactional boundaries with appropriate locking
- Validates record conflicts within the transaction
- Increments network serial to propagate changes
- Triggers peer updates asynchronously (as addressed from previous review)
103-160: LGTM!UpdateRecord correctly:
- Fetches existing record with appropriate locking
- Only re-validates conflicts when name/type/content changes (TTL changes correctly bypass conflict validation)
- Increments serial and triggers peer updates for all changes including TTL-only
162-207: LGTM!DeleteRecord correctly handles transactional deletion with proper locking, event logging, and peer notification.
209-236: LGTM!The zone membership check correctly enforces label boundary (exact match or proper subdomain with dot prefix), and the CNAME/duplicate conflict detection logic is sound.
management/server/store/sql_store_test.go (3)
25-26: LGTM!Imports correctly added for the new zones and records test coverage.
3723-3879: LGTM!Comprehensive test coverage for zone CRUD operations with proper table-driven tests for error cases and field validation.
3881-4126: LGTM!Thorough test coverage for DNS record operations including:
- Multiple record types (A, AAAA, CNAME)
- Name-based filtering
- Bulk deletion
- Proper error handling verification
management/server/store/store.go (3)
26-27: LGTM!Imports correctly added for the new zone and record types used in the interface.
208-213: LGTM!Zone interface methods follow the established patterns in the Store interface with consistent parameter ordering and locking strength handling for read operations.
215-222: LGTM!DNS record interface methods are comprehensive and properly scoped to zones:
- CRUD operations with correct parameter ordering
- Zone-scoped bulk operations (GetZoneDNSRecords, DeleteZoneDNSRecords)
- Name-based filtering (GetZoneDNSRecordsByName)
management/server/store/sql_store.go (4)
30-31: LGTM: Imports and migration setup.The new zones and records imports are correctly added, and the entities are properly registered in AutoMigrate for schema management.
Also applies to: 118-118
4134-4134: Previous review issues resolved.The typo in the error message (line 4134) has been corrected, and
DeleteDNSRecordnow properly scopes deletion by all three identifiers (accountID,zoneID, andrecordID), fixing the data integrity concern from the earlier review.Also applies to: 4221-4222
4131-4139: LGTM: Zone and DNS record CRUD operations.The remaining CRUD implementations follow consistent patterns throughout the file:
- Proper locking strength support on retrieval operations
- Appropriate error logging and status error types
- Correct use of
NewZoneNotFoundErrorandNewDNSRecordNotFoundError- Consistent query conditions (e.g.,
accountAndIDQueryConditionfor scoped operations)DeleteZoneDNSRecordscorrectly omitsRowsAffectedcheck since deleting zero records for a zone is validThe implementations integrate well with the existing store architecture.
Also applies to: 4165-4199, 4201-4219, 4235-4295
4151-4163: The zone deletion is properly handled at the manager layer. BeforeDeleteZoneis called in the store (line 162 in manager.go),DeleteZoneDNSRecordsis invoked first (line 157), ensuring all associated DNS records are removed within a transaction. This orchestration prevents both foreign key constraint errors and orphaned records. The simple delete implementation insql_store.gois appropriate since cascade logic is correctly managed at the manager layer.Likely an incorrect or invalid review comment.



Describe your changes
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Tests
Activity
Chores
✏️ Tip: You can customize this high-level summary in your review settings.