Files
netbird-kubernetes-operator/internal/controller/nbresource_controller.go
T
Philip LaineandGitHub 60ce12c74e Update Netbird dependency and Golang version (#112)
This change updates the Netbird dependency to the latest version to
enabled #111 to be implemented with future API additions. This change
requires updating the Go version as the upstream dependency requires it.

An interesting aspect that was required was to pin the dex dependency to
v2. It seems like the Netbird go.mod is doing something unexpected with
their version.

Signed-off-by: Philip Laine <philip.laine@gmail.com>
2026-03-03 10:31:38 +01:00

697 lines
23 KiB
Go

package controller
import (
"context"
nerrors "errors"
"fmt"
"slices"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/log"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/go-logr/logr"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
"github.com/netbirdio/kubernetes-operator/internal/util"
netbird "github.com/netbirdio/netbird/shared/management/client/rest"
"github.com/netbirdio/netbird/shared/management/http/api"
)
var (
errDuplicateResource = fmt.Errorf("duplicate resource")
)
// NBResourceReconciler reconciles a NBResource object
type NBResourceReconciler struct {
client.Client
Scheme *runtime.Scheme
APIKey string
ManagementURL string
AllowAutomaticPolicyCreation bool
ClusterName string
DefaultLabels map[string]string
netbird *netbird.Client
}
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) {
logger := ctrl.Log.WithName("NBResource").WithValues("namespace", req.Namespace, "name", req.Name)
logger.Info("Reconciling NBResource")
nbResource := &netbirdiov1.NBResource{}
err = r.Client.Get(ctx, req.NamespacedName, nbResource)
if err != nil {
if !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error getting NBResource", "err", err)
}
return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil
}
originalResource := nbResource.DeepCopy()
defer func() {
if err != nil {
// double check result is nil, otherwise error is not printed
// and exponential backoff doesn't work properly
res = ctrl.Result{}
return
}
if originalResource.DeletionTimestamp != nil && len(nbResource.Finalizers) == 0 {
return
}
if !originalResource.Status.Equal(nbResource.Status) {
updateErr := r.Client.Status().Update(ctx, nbResource)
if updateErr != nil {
err = updateErr
}
}
if !res.Requeue && res.RequeueAfter == 0 {
res.RequeueAfter = defaultRequeueAfter
}
}()
if nbResource.DeletionTimestamp != nil {
if len(nbResource.Finalizers) == 0 {
return ctrl.Result{}, nil
}
return ctrl.Result{}, r.handleDelete(ctx, req, nbResource, logger)
}
groupIDs, result, err := r.handleGroups(ctx, req, nbResource, logger)
if result != nil {
nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err))
return *result, err
}
resource, err := r.handleNetBirdResource(ctx, nbResource, groupIDs, logger)
if err != nil && nerrors.Is(err, errDuplicateResource) {
return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil
}
if err != nil {
nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling NetBird Network Resource: %v", err))
return ctrl.Result{}, err
}
// resource is only nil if requeue is expected
if resource == nil {
return ctrl.Result{Requeue: true}, nil
}
err = r.handleGroupUpdate(ctx, nbResource, groupIDs, resource, logger)
if err != nil {
nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling groups: %v", err))
return ctrl.Result{}, err
}
err = r.handlePolicy(ctx, req, nbResource, groupIDs, logger)
if err != nil {
nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling policy changes: %v", err))
return ctrl.Result{}, err
}
nbResource.Status.Conditions = netbirdiov1.NBConditionTrue()
return ctrl.Result{}, nil
}
func (r *NBResourceReconciler) handlePolicyCreate(ctx context.Context, nbResource *netbirdiov1.NBResource, req ctrl.Request, policy string, nbPolicy *netbirdiov1.NBPolicy, logger logr.Logger) error {
if len(nbResource.Spec.PolicySourceGroups) == 0 {
logger.Error(errInvalidValue, "Cannot auto-generate policy, missing source groups.")
return fmt.Errorf("cannot auto-generate policy, missing source groups")
}
name := nbResource.Spec.PolicyFriendlyName[policy]
if name == "" {
name = fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName)
}
generatedName := fmt.Sprintf("%s-%s-%s", policy, req.Namespace, req.Name)
*nbPolicy = netbirdiov1.NBPolicy{
ObjectMeta: v1.ObjectMeta{
Name: generatedName,
Annotations: map[string]string{"netbird.io/generated-by": req.NamespacedName.String()},
Finalizers: []string{"netbird.io/cleanup"},
Labels: r.DefaultLabels,
},
Spec: netbirdiov1.NBPolicySpec{
Name: name,
Description: "Generated by " + req.NamespacedName.String(),
SourceGroups: nbResource.Spec.PolicySourceGroups,
Bidirectional: true,
},
}
err := r.Client.Create(ctx, nbPolicy)
if errors.IsAlreadyExists(err) {
err = r.Client.Get(ctx, types.NamespacedName{Name: generatedName}, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
if nbPolicy.Annotations == nil {
nbPolicy.Annotations = make(map[string]string)
}
nbPolicy.Labels = r.DefaultLabels
nbPolicy.Annotations["netbird.io/generated-by"] = req.NamespacedName.String()
nbPolicy.Spec = netbirdiov1.NBPolicySpec{
Name: name,
Description: "Generated by " + req.NamespacedName.String(),
SourceGroups: nbResource.Spec.PolicySourceGroups,
Bidirectional: true,
}
err = r.Client.Update(ctx, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
} else if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
if nbResource.Status.PolicyNameMapping == nil {
nbResource.Status.PolicyNameMapping = make(map[string]string)
}
nbResource.Status.PolicyNameMapping[policy] = generatedName
nbResource.Status.PolicySourceGroups = nbResource.Spec.PolicySourceGroups
nbResource.Status.PolicyFriendlyName = nbResource.Spec.PolicyFriendlyName
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
err = r.Client.Status().Update(ctx, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
return nil
}
func (r *NBResourceReconciler) handlePolicyAddUpdate(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, policy string, groupIDs []string, logger logr.Logger) error {
var nbPolicy netbirdiov1.NBPolicy
updatePolicyStatus := false
kubernetesPolicyName := policy
if v, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
kubernetesPolicyName = v
}
err := r.Client.Get(ctx, types.NamespacedName{Name: kubernetesPolicyName}, &nbPolicy)
if errors.IsNotFound(err) && r.AllowAutomaticPolicyCreation {
err = r.handlePolicyCreate(ctx, nbResource, req, policy, &nbPolicy, logger)
if err != nil {
return err
}
} else if errors.IsNotFound(err) && !r.AllowAutomaticPolicyCreation {
logger.Info("automatic policy creation is not allowed")
return nil
} else if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.TCPPorts, nbResource.Status.TCPPorts) {
nbResource.Status.TCPPorts = nbResource.Spec.TCPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.UDPPorts, nbResource.Status.UDPPorts) {
nbResource.Status.UDPPorts = nbResource.Spec.UDPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Status.Groups, groupIDs) {
nbResource.Status.Groups = groupIDs
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if _, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
updatePolicySpec := false
if v, ok := nbPolicy.Annotations["netbird.io/generated-by"]; !ok || v != req.NamespacedName.String() {
if nbPolicy.Annotations == nil {
nbPolicy.Annotations = make(map[string]string)
}
nbPolicy.Annotations["netbird.io/generated-by"] = req.NamespacedName.String()
updatePolicySpec = true
}
if v, ok := nbResource.Spec.PolicyFriendlyName[policy]; ok {
if nbPolicy.Spec.Name != v {
nbPolicy.Spec.Name = v
updatePolicySpec = true
}
} else {
if nbPolicy.Spec.Name != fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName) {
nbPolicy.Spec.Name = fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName)
updatePolicySpec = true
}
}
if nbPolicy.Spec.Description != "Generated by "+req.NamespacedName.String() {
nbPolicy.Spec.Description = "Generated by " + req.NamespacedName.String()
updatePolicySpec = true
}
if !util.Equivalent(nbPolicy.Spec.SourceGroups, nbResource.Spec.PolicySourceGroups) {
nbPolicy.Spec.SourceGroups = nbResource.Spec.PolicySourceGroups
updatePolicySpec = true
}
if updatePolicySpec {
err := r.Client.Update(ctx, &nbPolicy)
if err != nil {
return err
}
}
}
if updatePolicyStatus {
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
return nil
}
func (r *NBResourceReconciler) handlePolicyDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, specPolicies []string, policy string, logger logr.Logger) error {
var nbPolicy netbirdiov1.NBPolicy
if !util.Contains(specPolicies, policy) {
kubeName := policy
if v, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
kubeName = v
}
err := r.Client.Get(ctx, types.NamespacedName{Name: kubeName}, &nbPolicy)
if !errors.IsNotFound(err) {
if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if _, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
// Delete Policy
err := r.Client.Delete(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error deleting NBPolicy", "err", err, "policyName", policy)
return err
}
delete(nbResource.Status.PolicyNameMapping, policy)
} else if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
}
}
return nil
}
// handlePolicy update NBPolicy if defined to add self reference to policy status
func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) error {
if nbResource.Status.PolicyName == nil && nbResource.Spec.PolicyName == "" {
return nil
}
specPolicies := util.SplitTrim(nbResource.Spec.PolicyName, ",")
var statusPolicies []string
if nbResource.Status.PolicyName != nil {
statusPolicies = util.SplitTrim(*nbResource.Status.PolicyName, ",")
}
for _, policy := range specPolicies {
err := r.handlePolicyAddUpdate(ctx, req, nbResource, policy, groupIDs, logger)
if err != nil {
return err
}
}
for _, policy := range statusPolicies {
err := r.handlePolicyDelete(ctx, req, nbResource, specPolicies, policy, logger)
if err != nil {
return err
}
}
if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbResource.Spec.PolicyName {
nbResource.Status.PolicyName = &nbResource.Spec.PolicyName
}
return nil
}
// handleGroupUpdate update network resource groups
func (r *NBResourceReconciler) handleGroupUpdate(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, resource *api.NetworkResource, logger logr.Logger) error {
// Handle possible updated group IDs
groupIDMap := make(map[string]interface{})
for _, g := range groupIDs {
groupIDMap[g] = nil
}
diffFound := len(groupIDs) != len(resource.Groups)
for _, g := range resource.Groups {
if _, ok := groupIDMap[g.Id]; !ok {
diffFound = true
}
}
if diffFound {
_, err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Update(ctx, resource.Id, api.NetworkResourceRequest{
Name: nbResource.Spec.Name,
Description: &networkDescription,
Address: nbResource.Spec.Address,
Enabled: true,
Groups: groupIDs,
})
if err != nil {
logger.Error(errNetBirdAPI, "error updating resource", "err", err)
return err
}
}
return nil
}
// handleNetBirdResource sync NetBird Network Resource
func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) (*api.NetworkResource, error) {
var resource *api.NetworkResource
var err error
if nbResource.Status.NetworkResourceID != nil {
resource, err = r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Get(ctx, *nbResource.Status.NetworkResourceID)
if err != nil && !strings.Contains(err.Error(), "not found") {
logger.Error(errNetBirdAPI, "error getting network resource", "err", err)
return nil, err
}
}
// 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 = netbirdiov1.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
} else if resource == nil {
// Status remembers networkResourceID but resource was deleted elsewhere
// remove networkID from status and re-enqueue
nbResource.Status.NetworkResourceID = nil
} else {
resourceGroups := make([]string, 0, len(resource.Groups))
for _, v := range resource.Groups {
resourceGroups = append(resourceGroups, v.Id)
}
if resource.Address != nbResource.Spec.Address ||
!resource.Enabled ||
!util.Equivalent(resourceGroups, groupIDs) ||
*resource.Description != networkDescription ||
resource.Name != nbResource.Spec.Name {
_, err = r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Update(ctx, *nbResource.Status.NetworkResourceID, api.NetworkResourceRequest{
Address: nbResource.Spec.Address,
Enabled: true,
Groups: groupIDs,
Description: &networkDescription,
Name: nbResource.Spec.Name,
})
if err != nil {
return resource, err
}
}
}
return resource, nil
}
// handleGroups create NBGroup objects for each group specified in NBResource
func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) ([]string, *ctrl.Result, error) {
nbGroupList := netbirdiov1.NBGroupList{}
err := r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace})
if err != nil {
logger.Error(errKubernetesAPI, "error listing NBGroup", "err", err)
return nil, nil, err
}
for _, g := range nbGroupList.Items {
ownerIndex := -1
for idx, o := range g.OwnerReferences {
if o.UID == nbResource.UID {
ownerIndex = idx
break
}
}
if ownerIndex == -1 {
continue
}
if util.Contains(nbResource.Spec.Groups, g.Spec.Name) {
continue
}
if len(g.OwnerReferences) > 1 {
g.OwnerReferences = slices.Delete(g.OwnerReferences, ownerIndex, ownerIndex+1)
err = r.Client.Update(ctx, &g)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return nil, nil, err
}
} else if len(g.OwnerReferences) == 1 {
g.Finalizers = util.Without(g.Finalizers, "netbird.io/resource-cleanup")
err = r.Client.Update(ctx, &g)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return nil, nil, err
}
}
}
var groupIDs []string
for _, groupName := range nbResource.Spec.Groups {
nbGroup := netbirdiov1.NBGroup{}
groupNameRFC := strings.ToLower(groupName)
groupNameRFC = strings.ReplaceAll(groupNameRFC, " ", "-")
err := r.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: groupNameRFC}, &nbGroup)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error getting NBGroup", "err", err)
return nil, &ctrl.Result{}, err
} else if errors.IsNotFound(err) {
// Create NBGroup
nbGroup = netbirdiov1.NBGroup{
ObjectMeta: v1.ObjectMeta{
Name: groupNameRFC,
Namespace: nbResource.Namespace,
OwnerReferences: []v1.OwnerReference{
{
APIVersion: netbirdiov1.GroupVersion.Identifier(),
Kind: "NBResource",
Name: nbResource.Name,
UID: nbResource.UID,
BlockOwnerDeletion: util.Ptr(true),
},
},
Finalizers: []string{"netbird.io/group-cleanup", "netbird.io/resource-cleanup"},
Labels: r.DefaultLabels,
},
Spec: netbirdiov1.NBGroupSpec{
Name: groupName,
},
}
err = r.Client.Create(ctx, &nbGroup)
if err != nil {
logger.Error(errKubernetesAPI, "error creating NBGroup", "err", err)
return nil, &ctrl.Result{}, err
}
continue
} else {
// Add NBResource as owner to NBGroup if not already done
ownerExists := false
for _, o := range nbGroup.OwnerReferences {
if o.UID == nbResource.UID {
ownerExists = true
}
}
if !ownerExists {
nbGroup.OwnerReferences = append(nbGroup.OwnerReferences, v1.OwnerReference{
APIVersion: netbirdiov1.GroupVersion.Identifier(),
Kind: "NBResource",
Name: nbResource.Name,
UID: nbResource.UID,
BlockOwnerDeletion: util.Ptr(true),
})
err = r.Client.Update(ctx, &nbGroup)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return nil, &ctrl.Result{}, err
}
}
}
if nbGroup.Status.GroupID != nil {
groupIDs = append(groupIDs, *nbGroup.Status.GroupID)
}
}
// if not all groups are ready, requeue
if len(groupIDs) != len(nbResource.Spec.Groups) {
return nil, &ctrl.Result{RequeueAfter: 5 * time.Second}, nil
}
return groupIDs, nil, nil
}
func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) error {
var svc corev1.Service
err := r.Client.Get(ctx, req.NamespacedName, &svc)
if !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error getting Service", "err", err, "svc", req.NamespacedName.String())
return err
} else if err == nil {
if _, ok := svc.Annotations[ServiceExposeAnnotation]; ok {
delete(svc.Annotations, ServiceExposeAnnotation)
err = r.Client.Update(ctx, &svc)
if err != nil {
logger.Error(errKubernetesAPI, "error updating Service", "err", err, "svc", req.NamespacedName.String())
return err
}
}
}
if nbResource.Status.PolicyName != nil {
for _, policy := range util.SplitTrim(*nbResource.Status.PolicyName, ",") {
var nbPolicy netbirdiov1.NBPolicy
err := r.Client.Get(ctx, types.NamespacedName{Name: policy}, &nbPolicy)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if !errors.IsNotFound(err) && util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
err = r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
return err
}
}
}
}
if nbResource.Status.NetworkResourceID != nil {
err := r.netbird.Networks.Resources(nbResource.Spec.NetworkID).Delete(ctx, *nbResource.Status.NetworkResourceID)
if err != nil && !strings.Contains(err.Error(), "not found") {
logger.Error(errNetBirdAPI, "error deleting resource", "err", err)
return err
}
nbResource.Status.NetworkResourceID = nil
}
nbGroupList := netbirdiov1.NBGroupList{}
err = r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace})
if err != nil {
logger.Error(errKubernetesAPI, "error listing NBGroup", "err", err)
return err
}
for _, g := range nbGroupList.Items {
ownerIndex := -1
for idx, o := range g.OwnerReferences {
if o.UID == nbResource.UID {
ownerIndex = idx
break
}
}
if ownerIndex == -1 {
continue
}
if len(g.OwnerReferences) > 1 {
g.OwnerReferences = slices.Delete(g.OwnerReferences, ownerIndex, ownerIndex+1)
err = r.Client.Update(ctx, &g)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return err
}
} else if len(g.OwnerReferences) == 1 {
g.Finalizers = util.Without(g.Finalizers, "netbird.io/resource-cleanup")
err = r.Client.Update(ctx, &g)
if err != nil && !errors.IsNotFound(err) {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return err
}
}
}
nbResource.Finalizers = nil
err = r.Client.Update(ctx, nbResource)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBGroup", "err", err)
return err
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *NBResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.netbird = netbird.New(r.ManagementURL, r.APIKey)
return ctrl.NewControllerManagedBy(mgr).
For(&netbirdiov1.NBResource{}).
Named("nbresource").
Watches(&netbirdiov1.NBGroup{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBResource{})).
Watches(&netbirdiov1.NBPolicy{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
if v, ok := obj.GetAnnotations()["netbird.io/generated-by"]; ok {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: strings.Split(v, "/")[0],
Name: strings.Split(v, "/")[1],
},
},
}
}
return nil
})).
Complete(r)
}