You've already forked ansible-netbird
mirror of
https://github.com/netbirdio/ansible-netbird.git
synced 2026-05-22 18:43:36 -07:00
fix: heal DNS nameserver group domains=null state on apply
A self-hosted NetBird dashboard crashed when loading the DNS → Nameservers
page because the installer-default "Public DNS" nameserver group was stored
with `domains: null` and the dashboard frontend does not null-guard that
field. The collection's apply cycle was preserving the null state rather
than healing it.
Four layers of fix:
- roles/export/templates/export/dns/nameservers.yml.j2 — always emit
`domains: []` on export instead of omitting the key when the API
returned null. Ensures the exported YAML carries an explicit list
into the apply cycle.
- roles/configure/tasks/main.yml — coerce `item.domains is null` to
`[]` via `default([], true)` before passing to the module, so that
a hand-edited YAML with `domains: null` still produces `[]`.
- plugins/module_utils/netbird_api.py — `update_nameserver_group` now
always includes `domains` in the PUT body and coerces `None → []`.
Previously it skipped the field on `None`, which preserved the
backend's null state.
- plugins/modules/netbird_dns.py — `nsgroup_needs_update` now treats
backend `domains: null` (or `groups: null`) as a heal-eligible change
against a list-valued desired state, so a PUT fires to coerce the
field. Previously it used `current.get('domains') or []` which
silently equated null with [] and returned "no change".
With these together, a `make safe-apply-test` cycle heals the
installer-seeded null state, and the dashboard loads. Verified against
netbird.cybersunset.net.
This commit is contained in:
@@ -636,10 +636,13 @@ class NetBirdAPI:
|
||||
data['nameservers'] = nameservers
|
||||
if description is not None:
|
||||
data['description'] = description
|
||||
# domains: never send null. The NetBird dashboard crashes when this
|
||||
# field is null, and the backend installer can seed it that way.
|
||||
# Coerce None to [] and always include it so an update cycle heals
|
||||
# pre-existing null state rather than preserving it.
|
||||
data['domains'] = [] if domains is None else domains
|
||||
if groups is not None:
|
||||
data['groups'] = groups
|
||||
if domains is not None:
|
||||
data['domains'] = domains
|
||||
if enabled is not None:
|
||||
data['enabled'] = enabled
|
||||
if primary is not None:
|
||||
|
||||
@@ -270,17 +270,24 @@ def nsgroup_needs_update(current, params):
|
||||
if nameservers_need_update(current.get('nameservers'), params['nameservers']):
|
||||
return True
|
||||
|
||||
# Check groups
|
||||
# Check groups -- treat backend null as a heal-eligible change
|
||||
if params.get('groups') is not None:
|
||||
current_groups = set(extract_ids(current.get('groups') or []))
|
||||
desired_groups = set(extract_ids(params['groups'] or []))
|
||||
current_raw_groups = current.get('groups')
|
||||
if current_raw_groups is None:
|
||||
return True
|
||||
current_groups = set(extract_ids(current_raw_groups))
|
||||
desired_groups = set(extract_ids(params['groups']))
|
||||
if current_groups != desired_groups:
|
||||
return True
|
||||
|
||||
# Check domains
|
||||
|
||||
# Check domains -- treat backend null as a heal-eligible change
|
||||
# (the dashboard crashes on null; an update must fire to coerce it to [])
|
||||
if params.get('domains') is not None:
|
||||
current_domains = set(current.get('domains') or [])
|
||||
desired_domains = set(params['domains'] or [])
|
||||
current_raw_domains = current.get('domains')
|
||||
if current_raw_domains is None:
|
||||
return True
|
||||
current_domains = set(current_raw_domains)
|
||||
desired_domains = set(params['domains'])
|
||||
if current_domains != desired_domains:
|
||||
return True
|
||||
|
||||
|
||||
@@ -342,8 +342,10 @@
|
||||
name: "{{ item.name }}"
|
||||
description: "{{ item.description | default(omit) }}"
|
||||
nameservers: "{{ item.nameservers }}"
|
||||
groups: "{{ item.groups | default([]) | map('extract', group_ids) | list }}"
|
||||
domains: "{{ item.domains | default(omit) }}"
|
||||
groups: "{{ item.groups | default([], true) | map('extract', group_ids) | list }}"
|
||||
# Coerce null/undefined to [] so the apply path heals backend state
|
||||
# where domains was seeded as null (the dashboard crashes on null).
|
||||
domains: "{{ item.domains | default([], true) }}"
|
||||
enabled: "{{ item.enabled | default(true) }}"
|
||||
primary: "{{ item.primary | default(false) }}"
|
||||
search_domains_enabled: "{{ item.search_domains_enabled | default(omit) }}"
|
||||
|
||||
@@ -33,6 +33,8 @@ netbird_dns_nameserver_groups:
|
||||
{% for domain in ns.domains %}
|
||||
- "{{ domain }}"
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
domains: []
|
||||
{% endif %}
|
||||
enabled: {{ ns.enabled | default(true) | lower }}
|
||||
primary: {{ ns.primary | default(false) | lower }}
|
||||
|
||||
Reference in New Issue
Block a user