Version: netbird-operator v0.7.0 (ghcr.io/netbirdio/netbird-operator:v0.7.0), reproduced against main
Location: internal/controller/nbresource_controller.go, handleNetBirdResource(), lines 422-441
// Create/Update upstream network resource
if nbResource.Status.NetworkResourceID == nil && resource == nil {
resource, err := r.Netbird.Networks.Resources(nbResource.Spec.NetworkID).Create(ctx, api.NetworkResourceRequest{
Address: nbResource.Spec.Address,
Enabled: true,
Groups: groupIDs,
Description: &networkDescription,
Name: nbResource.Spec.Name,
})
if err != nil && strings.Contains(err.Error(), "already exists") {
log.Log.Error(errNetBirdAPI, "network resource with the same name already exists", "err", err)
nbResource.Status.Conditions = nbv1.NBConditionFalse("DuplicateName", "Resource name already exists")
return nil, errDuplicateResource
}
if err != nil {
logger.Error(errNetBirdAPI, "error creating resource", "err", err)
return nil, err
}
nbResource.Status.NetworkResourceID = &resource.Id
}
Repro:
- Create a network resource named
X on network net-1 via the NetBird management API directly (bootstrap script, another NBResource CR that was deleted without owning status.networkResourceID cleanup, or a prior failed apply).
- Apply an
NBResource CR with spec.name: X, spec.networkID: net-1, and empty status (fresh CR, status.networkResourceID == nil).
- Reconcile calls
Create(), which returns resource with name X already exists (netbird API error).
Actual: The error is matched by strings.Contains(err.Error(), "already exists"), status.conditions is set to reason=DuplicateName, and errDuplicateResource is returned. Reconcile() (lines 108-111) treats that as a non-error and just requeues after defaultRequeueAfter — forever, since nothing ever looks the existing resource up by name to adopt it. status.networkResourceID is never populated, so handlePolicy() (line 129, gated on status.PolicyName/spec) never runs, and status.tcpPorts/status.udpPorts never propagate to any dependent policy.
Expected: Same adopt pattern already implemented for groups in NBGroupReconciler.syncNetBirdGroup() (internal/controller/nbgroup_controller.go, lines 84-116):
groups, err := r.Netbird.Groups.List(ctx)
...
for _, g := range groups {
if g.Name == nbGroup.Spec.Name {
group = &g
}
}
if nbGroup.Status.GroupID == nil && group == nil {
// create
} else if nbGroup.Status.GroupID == nil && group != nil {
logger.Info("NBGroup: Found group with same name on NetBird", "name", nbGroup.Spec.Name, "id", group.Id)
nbGroup.Status.GroupID = &group.Id
nbGroup.Status.Conditions = nbv1.NBConditionTrue()
}
NBResource should look up the existing network resource by name (or handle the already exists error by fetching-and-adopting) and populate status.networkResourceID/update it, instead of only handling Create().
Impact: Any NBResource whose name collides with an object already present on the NetBird management API is permanently stuck in a DuplicateName condition with no networkResourceID, no port propagation, and an indefinite requeue loop — the reconcile can never converge.
Version: netbird-operator v0.7.0 (
ghcr.io/netbirdio/netbird-operator:v0.7.0), reproduced againstmainLocation:
internal/controller/nbresource_controller.go,handleNetBirdResource(), lines 422-441Repro:
Xon networknet-1via the NetBird management API directly (bootstrap script, another NBResource CR that was deleted without owningstatus.networkResourceIDcleanup, or a prior failed apply).NBResourceCR withspec.name: X,spec.networkID: net-1, and emptystatus(fresh CR,status.networkResourceID == nil).Create(), which returnsresource with name X already exists (netbird API error).Actual: The error is matched by
strings.Contains(err.Error(), "already exists"),status.conditionsis set toreason=DuplicateName, anderrDuplicateResourceis returned.Reconcile()(lines 108-111) treats that as a non-error and just requeues afterdefaultRequeueAfter— forever, since nothing ever looks the existing resource up by name to adopt it.status.networkResourceIDis never populated, sohandlePolicy()(line 129, gated onstatus.PolicyName/spec) never runs, andstatus.tcpPorts/status.udpPortsnever propagate to any dependent policy.Expected: Same adopt pattern already implemented for groups in
NBGroupReconciler.syncNetBirdGroup()(internal/controller/nbgroup_controller.go, lines 84-116):NBResourceshould look up the existing network resource by name (or handle thealready existserror by fetching-and-adopting) and populatestatus.networkResourceID/update it, instead of only handlingCreate().Impact: Any
NBResourcewhose name collides with an object already present on the NetBird management API is permanently stuck in aDuplicateNamecondition with nonetworkResourceID, no port propagation, and an indefinite requeue loop — the reconcile can never converge.