16 Commits
Author SHA1 Message Date
Jack Carter 6f18e2cf5f refactor: move DNS nameserver null-coercion to module layer
Code-review follow-up on bbea38e. Two small cleanups, no behavior change:

- plugins/module_utils/netbird_api.py: revert update_nameserver_group's
  PUT-body construction for `domains` back to the partial-update pattern
  (`if domains is not None`) used by every other update_* method. The
  helper is now a pure HTTP primitive again, so future modules can rely
  on consistent partial-update semantics for this surface.

- plugins/modules/netbird_dns.py: coerce `domains` and `groups` from
  None to [] at the module layer, right before building update_params
  and calling the API helper. This keeps the null-heal intent local to
  the DNS module (where the dashboard-crash reason lives) and applies
  symmetrically to both list fields, closing the asymmetry between
  nsgroup_needs_update (already symmetric) and the PUT body.

Net effect is identical to bbea38e: installer-seeded `domains: null`
still heals to `[]` on apply, the dashboard still loads, the sister
repo's smoke/extended-CRUD assertions still pass. Only the layer at
which the coercion lives has moved.

Coexists cleanly with open PR #33 (fix/strict-name-resolution): both
branches touch roles/configure/tasks/main.yml at non-overlapping hunks.
2026-04-17 18:43:24 +02:00
Jack Carter bbea38eae0 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.
2026-04-17 18:03:39 +02:00
Jack Carter 9108d2e540 fix: null-guard DNS nameserver domains/groups on export and update
Two related crashes when a nameserver group has no domains or groups:
the API stores unset fields as null (not []), but the export template
and netbird_dns module both assumed list.

- roles/export/templates/export/dns/nameservers.yml.j2: add
  `is not none` guards on ns.groups and ns.domains. Matches the pattern
  already used on ns.description (line 14) and across zones.yml.j2.
  Without this, `None | length` crashes the export template and blocks
  every test that does a full export (iac, roundtrip, strict).

- plugins/modules/netbird_dns.py: in nsgroup_needs_update, use
  `current.get('domains') or []` instead of `current.get('domains', [])`.
  The .get default only applies when the key is missing, not when the
  value is None. Without this, `set(None)` crashes when diffing against
  a live API group that has `domains: null`.

Verified on live self-hosted NetBird: iac-selfhosted, roundtrip-selfhosted,
and strict-selfhosted now all pass (previously failed at the DNS export
or DNS update step).
2026-04-17 10:47:28 +02:00
Jack Carter 2e4efde08b fix: null guard sync_records() in netbird_dns_zone
list_dns_zone_records() returns null for zones with no records,
causing TypeError when iterating. Same pattern fixed in #20 for
other modules — this one was missed.
2026-03-31 11:59:11 +02:00
Jack Carter 2041cd2d89 fix: preserve existing field values on partial updates
Changes module behavior so that omitting auto_groups, peers, or
similar list fields on update preserves the existing values instead
of wiping them to [].

Modules changed:
- netbird_setup_key: auto_groups default [] -> None, preserve on update
- netbird_group: peers default [] -> None, preserve on update,
  normalize peer dicts to IDs
- netbird_user: auto_groups default [] -> None, preserve on update

Also:
- Reimplement get_current_user() since /api/users/me does not exist.
  Now lists users and matches by is_current flag, with explicit error
  on multi-user deployments without the flag.
- Configure role: use default(omit) for setup key auto_groups so
  the module-level preservation works through the role.
- Configure role: conditional auto_groups resolution (skip when
  auto_groups not defined in YAML config).
- Configure role: DNS zone distribution_groups now handles both
  group names and raw IDs (falls back to original value when not
  found in group_ids map).
- Updated DOCUMENTATION strings to remove stale default: [] and
  document preservation behavior.
2026-03-30 21:21:28 +02:00
Jack Carter 5d85c3afef fix: null guard all API list iterations and fix role set_fact parsing
- Add (items or []) to all find_*_by_* functions across 12 modules.
  The NetBird API returns null for empty collections, causing TypeError
  when iterating. Affects find_group_by_name, find_policy_by_name,
  find_setup_key_by_name, find_user_by_email, find_user_by_name,
  find_network_by_name, find_route_by_network_id, find_nsgroup_by_name,
  find_zone_by_name, find_posture_check_by_name, find_idp_by_name,
  find_invite_by_email, find_token_by_name.

- Add (or []) to sync_routers and sync_resources in netbird_network
  for the same reason (new networks have null router/resource lists).

- Fix export role network enrichment: .get('json', []) does not work
  on Ansible uri module results. Changed to .json | default([]).

- Fix set_fact parsing in export and configure roles: {{ result | to_json }}
  produces a JSON string, not a list. Added | from_json so downstream
  loop directives receive proper lists (3 occurrences in configure,
  1 in export).

- Use urllib.parse.urlencode for query parameter encoding instead of
  raw f-string interpolation to prevent URL breakage with special chars.
2026-03-30 21:18:13 +02:00
Tomas Kloda c42857cdb6 fixed comparison issue in other modules 2026-03-27 14:30:41 +01:00
Tomas Kloda d4868ae8d3 comparison fix 2026-03-27 14:20:05 +01:00
Tomas Kloda 1402b81b64 module id fixes 2026-03-27 10:59:57 +01:00
Tomas Kloda 9cc6ab4f65 fix data types 2026-03-27 10:48:18 +01:00
Tomas Kloda f40dcf17bb adding dns zones support 2026-03-26 18:57:00 +01:00
Tomas Kloda 0cb8dffb5a sync with v0.67.0 2026-03-24 16:34:41 +01:00
Tomas Kloda 7a7f458b63 adding support for other roles 2026-01-13 18:14:14 +01:00
Tomas Kloda 8fd8e05255 address fix 2025-12-18 11:32:20 +01:00
Tomas Kloda 1774298fb7 updates 2025-12-18 11:25:41 +01:00
Tomas Kloda 76a789cd98 initial commit 2025-12-17 16:19:29 +01:00