From 38445c58fae73df1ff218fd7bb97c8c6aacd0338 Mon Sep 17 00:00:00 2001 From: M Essam Hamed Date: Sun, 13 Apr 2025 23:00:23 +0200 Subject: [PATCH] Add support for policy auto-creation --- api/v1/nbresource_types.go | 20 +- api/v1/zz_generated.deepcopy.go | 31 ++ cmd/main.go | 29 +- helm/kubernetes-operator/Chart.yaml | 4 +- .../crds/netbird.io_nbresources.yaml | 20 + .../templates/deployment.yaml | 3 + helm/kubernetes-operator/values.yaml | 2 + internal/controller/nbpolicy_controller.go | 31 +- internal/controller/nbresource_controller.go | 346 ++++++++++++----- .../controller/nbresource_controller_test.go | 365 ++++++++++++++---- internal/controller/service_controller.go | 31 +- .../controller/service_controller_test.go | 42 ++ 12 files changed, 712 insertions(+), 212 deletions(-) diff --git a/api/v1/nbresource_types.go b/api/v1/nbresource_types.go index 65730c7..ebb256e 100644 --- a/api/v1/nbresource_types.go +++ b/api/v1/nbresource_types.go @@ -1,6 +1,8 @@ package v1 import ( + "maps" + "github.com/netbirdio/kubernetes-operator/internal/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -18,6 +20,10 @@ type NBResourceSpec struct { // +optional PolicyName string `json:"policyName,omitempty"` // +optional + PolicySourceGroups []string `json:"policySourceGroups,omitempty"` + // +optional + PolicyFriendlyName map[string]string `json:"policyFriendlyName,omitempty"` + // +optional TCPPorts []int32 `json:"tcpPorts,omitempty"` // +optional UDPPorts []int32 `json:"udpPorts,omitempty"` @@ -31,7 +37,8 @@ func (a NBResourceSpec) Equal(b NBResourceSpec) bool { util.Equivalent(a.Groups, b.Groups) && a.PolicyName == b.PolicyName && util.Equivalent(a.TCPPorts, b.TCPPorts) && - util.Equivalent(a.UDPPorts, b.UDPPorts) + util.Equivalent(a.UDPPorts, b.UDPPorts) && + util.Equivalent(a.PolicySourceGroups, b.PolicySourceGroups) } // NBResourceStatus defines the observed state of NBResource. @@ -47,7 +54,13 @@ type NBResourceStatus struct { // +optional Groups []string `json:"groups,omitempty"` // +optional + PolicySourceGroups []string `json:"policySourceGroups,omitempty"` + // +optional + PolicyFriendlyName map[string]string `json:"policyFriendlyName,omitempty"` + // +optional Conditions []NBCondition `json:"conditions,omitempty"` + // +optional + PolicyNameMapping map[string]string `json:"policyNameMapping"` } // Equal returns if NBResourceStatus is equal to this one @@ -57,7 +70,10 @@ func (a NBResourceStatus) Equal(b NBResourceStatus) bool { util.Equivalent(a.TCPPorts, b.TCPPorts) && util.Equivalent(a.UDPPorts, b.UDPPorts) && util.Equivalent(a.Groups, b.Groups) && - util.Equivalent(a.Conditions, b.Conditions) + util.Equivalent(a.Conditions, b.Conditions) && + util.Equivalent(a.PolicySourceGroups, b.PolicySourceGroups) && + maps.Equal(a.PolicyFriendlyName, b.PolicyFriendlyName) && + maps.Equal(a.PolicyNameMapping, b.PolicyNameMapping) } // +kubebuilder:object:root=true diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 123fa86..2ea79f4 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -329,6 +329,18 @@ func (in *NBResourceSpec) DeepCopyInto(out *NBResourceSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PolicySourceGroups != nil { + in, out := &in.PolicySourceGroups, &out.PolicySourceGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PolicyFriendlyName != nil { + in, out := &in.PolicyFriendlyName, &out.PolicyFriendlyName + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.TCPPorts != nil { in, out := &in.TCPPorts, &out.TCPPorts *out = make([]int32, len(*in)) @@ -379,6 +391,18 @@ func (in *NBResourceStatus) DeepCopyInto(out *NBResourceStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PolicySourceGroups != nil { + in, out := &in.PolicySourceGroups, &out.PolicySourceGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.PolicyFriendlyName != nil { + in, out := &in.PolicyFriendlyName, &out.PolicyFriendlyName + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]NBCondition, len(*in)) @@ -386,6 +410,13 @@ func (in *NBResourceStatus) DeepCopyInto(out *NBResourceStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.PolicyNameMapping != nil { + in, out := &in.PolicyNameMapping, &out.PolicyNameMapping + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBResourceStatus. diff --git a/cmd/main.go b/cmd/main.go index a130f4c..5b5d535 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -67,12 +67,13 @@ func init() { func main() { // NB Specific flags var ( - managementURL string - clientImage string - clusterName string - namespacedNetworks bool - clusterDNS string - netbirdAPIKey string + managementURL string + clientImage string + clusterName string + namespacedNetworks bool + clusterDNS string + netbirdAPIKey string + allowAutomaticPolicyCreation bool ) flag.StringVar(&managementURL, "netbird-management-url", "https://api.netbird.io", "Management service URL") flag.StringVar(&clientImage, "netbird-client-image", "netbirdio/netbird:latest", "Image for netbird client container") @@ -90,6 +91,12 @@ func main() { ) flag.StringVar(&clusterDNS, "cluster-dns", "svc.cluster.local", "Cluster DNS name") flag.StringVar(&netbirdAPIKey, "netbird-api-key", "", "API key for NetBird API operations") + flag.BoolVar( + &allowAutomaticPolicyCreation, + "allow-automatic-policy-creation", + false, + "Allow creating NBPolicy resources from annotations on Services", + ) // Controller generic flags var ( @@ -233,10 +240,12 @@ func main() { } if err = (&controller.NBResourceReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - APIKey: netbirdAPIKey, - ManagementURL: managementURL, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + APIKey: netbirdAPIKey, + ManagementURL: managementURL, + AllowAutomaticPolicyCreation: allowAutomaticPolicyCreation, + ClusterName: clusterName, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "NBResource") os.Exit(1) diff --git a/helm/kubernetes-operator/Chart.yaml b/helm/kubernetes-operator/Chart.yaml index 661c322..ec61de1 100644 --- a/helm/kubernetes-operator/Chart.yaml +++ b/helm/kubernetes-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: kubernetes-operator description: NetBird Kubernetes Operator type: application -version: 0.1.7 -appVersion: "0.1.2" +version: 0.1.8 +appVersion: "0.1.3" diff --git a/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml b/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml index 4e73570..b8ce582 100644 --- a/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml +++ b/helm/kubernetes-operator/crds/netbird.io_nbresources.yaml @@ -55,8 +55,16 @@ spec: x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf + policyFriendlyName: + additionalProperties: + type: string + type: object policyName: type: string + policySourceGroups: + items: + type: string + type: array tcpPorts: items: format: int32 @@ -116,8 +124,20 @@ spec: type: array networkResourceID: type: string + policyFriendlyName: + additionalProperties: + type: string + type: object policyName: type: string + policyNameMapping: + additionalProperties: + type: string + type: object + policySourceGroups: + items: + type: string + type: array tcpPorts: items: format: int32 diff --git a/helm/kubernetes-operator/templates/deployment.yaml b/helm/kubernetes-operator/templates/deployment.yaml index d7466d9..6394e74 100644 --- a/helm/kubernetes-operator/templates/deployment.yaml +++ b/helm/kubernetes-operator/templates/deployment.yaml @@ -60,6 +60,9 @@ spec: {{- if or .Values.netbirdAPI.key .Values.netbirdAPI.keyFromSecret }} - --netbird-api-key=$(NB_API_KEY) {{- end }} + {{- if .Values.ingress.allowAutomaticPolicyCreation }} + - --allow-automatic-policy-creation + {{- end }} ports: - name: webhook-server containerPort: {{ .Values.webhook.service.port }} diff --git a/helm/kubernetes-operator/values.yaml b/helm/kubernetes-operator/values.yaml index 02ef1aa..0386268 100644 --- a/helm/kubernetes-operator/values.yaml +++ b/helm/kubernetes-operator/values.yaml @@ -135,6 +135,8 @@ ingress: enabled: false # Create router per namespace, useful for strict networking requirements namespacedNetworks: false + # Allow creating policies through Service annotations + allowAutomaticPolicyCreation: false kubernetesAPI: enabled: false groups: [] diff --git a/internal/controller/nbpolicy_controller.go b/internal/controller/nbpolicy_controller.go index c784d56..423e75e 100644 --- a/internal/controller/nbpolicy_controller.go +++ b/internal/controller/nbpolicy_controller.go @@ -33,6 +33,7 @@ var ( errUnknownProtocol = fmt.Errorf("Unknown protocol") errKubernetesAPI = fmt.Errorf("kubernetes API error") errNetBirdAPI = fmt.Errorf("netbird API error") + errInvalidValue = fmt.Errorf("invalid value") ) const ( @@ -77,16 +78,28 @@ func (r *NBPolicyReconciler) mapResources(ctx context.Context, nbPolicy *netbird } for _, resource := range resources { - if resource.Status.PolicyName != nil && util.Contains(util.SplitTrim(*resource.Status.PolicyName, ","), nbPolicy.Name) { - // Groups - groups = append(groups, resource.Status.Groups...) + generatedBy := nbPolicy.Annotations["netbird.io/generated-by"] + generatedBy = strings.ReplaceAll(generatedBy, "/", "-") + if resource.Status.PolicyName == nil { + continue + } + resourcePolicies := util.SplitTrim(*resource.Status.PolicyName, ",") - for _, p := range resource.Spec.TCPPorts { - portMapping[protocolTCP][p] = nil - } - for _, p := range resource.Spec.UDPPorts { - portMapping[protocolUDP][p] = nil - } + if generatedBy == "" && !util.Contains(resourcePolicies, nbPolicy.Name) { + continue + } + + if generatedBy != "" && !util.Contains(resourcePolicies, strings.ReplaceAll(nbPolicy.Name, "-"+generatedBy, "")) { + continue + } + // Groups + groups = append(groups, resource.Status.Groups...) + + for _, p := range resource.Spec.TCPPorts { + portMapping[protocolTCP][p] = nil + } + for _, p := range resource.Spec.UDPPorts { + portMapping[protocolUDP][p] = nil } } diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go index 78b0244..e4e46da 100644 --- a/internal/controller/nbresource_controller.go +++ b/internal/controller/nbresource_controller.go @@ -14,6 +14,7 @@ import ( 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" @@ -25,10 +26,12 @@ import ( // NBResourceReconciler reconciles a NBResource object type NBResourceReconciler struct { client.Client - Scheme *runtime.Scheme - APIKey string - ManagementURL string - netbird *netbird.Client + Scheme *runtime.Scheme + APIKey string + ManagementURL string + AllowAutomaticPolicyCreation bool + ClusterName string + netbird *netbird.Client } // Reconcile is part of the main kubernetes reconciliation loop which aims to @@ -96,6 +99,7 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) 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() @@ -103,115 +107,236 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) 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"}, + }, + 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.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 } - var nbPolicy netbirdiov1.NBPolicy - if nbResource.Spec.PolicyName == "" && nbResource.Status.PolicyName != nil { - // Remove self reference from policy status - policies := util.SplitTrim(*nbResource.Status.PolicyName, ",") - for _, policyName := range policies { - err := r.Client.Get(ctx, types.NamespacedName{Name: policyName}, &nbPolicy) - nbResource.Status.PolicyName = nil - if err != nil { - logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policyName) - return err - } - 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", policyName) - return err - } - } + 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 } - } else { - specPolicies := util.SplitTrim(nbResource.Spec.PolicyName, ",") - var statusPolicies []string - if nbResource.Status.PolicyName != nil { - statusPolicies = util.SplitTrim(*nbResource.Status.PolicyName, ",") + } + + for _, policy := range statusPolicies { + err := r.handlePolicyDelete(ctx, req, nbResource, specPolicies, policy, logger) + if err != nil { + return err } + } - for _, policy := range specPolicies { - updatePolicyStatus := false - - err := r.Client.Get(ctx, types.NamespacedName{Name: policy}, &nbPolicy) - if err != nil { - logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy) - return err - } - if !util.Contains(statusPolicies, policy) { - // New - if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { - nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) - updatePolicyStatus = true - } - } else { - // Check update - if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) { - nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) - nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()} - 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 updatePolicyStatus { - err := r.Client.Status().Update(ctx, &nbPolicy) - if err != nil { - logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy) - return err - } - } - } - - for _, policy := range statusPolicies { - // Delete - if !util.Contains(specPolicies, policy) { - err := r.Client.Get(ctx, types.NamespacedName{Name: policy}, &nbPolicy) - if !errors.IsNotFound(err) { - 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 = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) - err := r.Client.Status().Update(ctx, &nbPolicy) - if err != nil { - logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy) - return err - } - } - } - } - } - - if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbResource.Spec.PolicyName { - nbResource.Status.PolicyName = &nbResource.Spec.PolicyName - } + if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbResource.Spec.PolicyName { + nbResource.Status.PolicyName = &nbResource.Spec.PolicyName } return nil @@ -510,5 +635,18 @@ func (r *NBResourceReconciler) SetupWithManager(mgr ctrl.Manager) error { 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) } diff --git a/internal/controller/nbresource_controller_test.go b/internal/controller/nbresource_controller_test.go index 916a95e..3a7aa51 100644 --- a/internal/controller/nbresource_controller_test.go +++ b/internal/controller/nbresource_controller_test.go @@ -26,6 +26,7 @@ import ( var _ = Describe("NBResource Controller", func() { Context("When reconciling a resource", func() { const resourceName = "test-resource" + const policyGenName = "test-gen" ctx := context.Background() @@ -45,9 +46,10 @@ var _ = Describe("NBResource Controller", func() { server = httptest.NewServer(mux) netbirdClient = netbird.New(server.URL, "ABC") controllerReconciler = &NBResourceReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - netbird: netbirdClient, + Client: k8sClient, + Scheme: k8sClient.Scheme(), + netbird: netbirdClient, + ClusterName: "kubernetes", } By("creating the custom resource for the Kind NBResource") @@ -315,65 +317,48 @@ var _ = Describe("NBResource Controller", func() { }) When("Policy is specified", Ordered, func() { - BeforeAll(func() { - nbPolicy := &netbirdiov1.NBPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-a", - }, - Spec: netbirdiov1.NBPolicySpec{ - Name: "Test A", - SourceGroups: []string{"All"}, - }, - } - Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed()) + When("Policy Exists", func() { + BeforeAll(func() { + nbPolicy := &netbirdiov1.NBPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-a", + }, + Spec: netbirdiov1.NBPolicySpec{ + Name: "Test A", + SourceGroups: []string{"All"}, + }, + } + Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed()) - nbPolicy = &netbirdiov1.NBPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-b", - }, - Spec: netbirdiov1.NBPolicySpec{ - Name: "Test B", - SourceGroups: []string{"All"}, - }, - } - Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed()) - }) - - AfterAll(func() { - nbPolicy := &netbirdiov1.NBPolicy{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy) - if !errors.IsNotFound(err) { - Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed()) - } - - nbPolicy = &netbirdiov1.NBPolicy{} - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy) - if !errors.IsNotFound(err) { - Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed()) - } - }) - It("should update policy status", func() { - nbresource.Spec.PolicyName = "test-a" - Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, + nbPolicy = &netbirdiov1.NBPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-b", + }, + Spec: netbirdiov1.NBPolicySpec{ + Name: "Test B", + SourceGroups: []string{"All"}, + }, + } + Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed()) }) - Expect(err).NotTo(HaveOccurred()) - nbPolicy := &netbirdiov1.NBPolicy{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) - Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) - }) + AfterAll(func() { + nbPolicy := &netbirdiov1.NBPolicy{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy) + if !errors.IsNotFound(err) { + Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed()) + } - When("Policy is updated", func() { - It("should remove old reference and add new reference", func() { - nbresource.Spec.PolicyName = "test-b" + nbPolicy = &netbirdiov1.NBPolicy{} + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy) + if !errors.IsNotFound(err) { + Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed()) + } + }) + It("should update policy status", func() { + nbresource.Spec.PolicyName = "test-a" Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) - nbresource.Status.PolicyName = util.Ptr("test-a") - Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed()) - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespacedName, }) @@ -381,35 +366,257 @@ var _ = Describe("NBResource Controller", func() { nbPolicy := &netbirdiov1.NBPolicy{} Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) - Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource")) - - nbPolicy = &netbirdiov1.NBPolicy{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)).To(Succeed()) Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) }) + + When("Policy is updated", func() { + It("should remove old reference and add new reference", func() { + nbresource.Spec.PolicyName = "test-b" + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + nbresource.Status.PolicyName = util.Ptr("test-a") + Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource")) + + nbPolicy = &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) + }) + }) + + When("Policy is removed", func() { + It("should remove old reference", func() { + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) + nbPolicy.Status.ManagedServiceList = []string{"default/test-resource"} + Expect(k8sClient.Status().Update(ctx, nbPolicy)).To(Succeed()) + + nbresource.Spec.PolicyName = "" + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + nbresource.Status.PolicyName = util.Ptr("test-a") + Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + nbPolicy = &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource")) + }) + }) }) - When("Policy is removed", func() { - It("should remove old reference", func() { - nbPolicy := &netbirdiov1.NBPolicy{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) - nbPolicy.Status.ManagedServiceList = []string{"default/test-resource"} - Expect(k8sClient.Status().Update(ctx, nbPolicy)).To(Succeed()) - - nbresource.Spec.PolicyName = "" - Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) - - nbresource.Status.PolicyName = util.Ptr("test-a") - Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed()) - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, + When("Policy doesn't exist", func() { + When("Policy auto-creation is enabled", func() { + BeforeEach(func() { + controllerReconciler.AllowAutomaticPolicyCreation = true + }) + AfterEach(func() { + nbPolicy := &netbirdiov1.NBPolicy{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-gen-" + nbresource.Namespace + "-" + nbresource.Name}, nbPolicy) + if !errors.IsNotFound(err) { + if len(nbPolicy.Finalizers) > 0 { + nbPolicy.Finalizers = nil + Expect(k8sClient.Update(ctx, nbPolicy)).To(Succeed()) + } + err = k8sClient.Delete(ctx, nbPolicy) + if !errors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred()) + } + } }) - Expect(err).NotTo(HaveOccurred()) - nbPolicy = &netbirdiov1.NBPolicy{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed()) - Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource")) + It("should create policy", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) + }) + + When("Source groups is not defined", func() { + It("should return error", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = nil + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).To(HaveOccurred()) + }) + }) + + When("Friendly name is specified", func() { + It("should override policy name", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + nbresource.Spec.PolicyFriendlyName = map[string]string{policyGenName: "UnitTest"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) + Expect(nbPolicy.Spec.Name).To(Equal("UnitTest")) + }) + }) + + When("Policy already exists", func() { + It("should update it", func() { + nbPolicy := &netbirdiov1.NBPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gen-default-test-resource", + }, + Spec: netbirdiov1.NBPolicySpec{ + Name: "Test", + Description: "Test", + SourceGroups: []string{"toast"}, + Bidirectional: false, + }, + } + Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed()) + + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy = &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) + Expect(nbPolicy.Spec.Name).To(Equal("Autogenerated policy for resource default/test-resource in cluster kubernetes")) + Expect(nbPolicy.Spec.Bidirectional).To(BeTrue()) + }) + }) + + When("Policy settings are updated", func() { + It("should update it", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + nbresource.Spec.PolicyFriendlyName = map[string]string{policyGenName: "UnitTest"} + + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource")) + Expect(nbPolicy.Spec.Name).To(Equal("UnitTest")) + }) + }) + + When("Policy is changed outside controller", func() { + It("should update it", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + nbPolicy.Spec.Name = "Meow" + nbPolicy.Annotations = nil + nbPolicy.Spec.Description = "woeM" + nbPolicy.Spec.SourceGroups = []string{"est"} + Expect(k8sClient.Update(ctx, nbPolicy)).To(Succeed()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.Spec.Name).To(Equal("Autogenerated policy for resource default/test-resource in cluster kubernetes")) + Expect(nbPolicy.Annotations["netbird.io/generated-by"]).To(Equal("default/test-resource")) + Expect(nbPolicy.Spec.Description).To(Equal("Generated by default/test-resource")) + Expect(nbPolicy.Spec.SourceGroups).To(BeEquivalentTo([]string{"test"})) + }) + }) + + When("Policy is removed", func() { + It("should delete NBPolicy", func() { + nbresource.Spec.PolicyName = policyGenName + nbresource.Spec.PolicySourceGroups = []string{"test"} + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed()) + Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName)) + + nbPolicy := &netbirdiov1.NBPolicy{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + nbresource.Spec.PolicyName = "" + Expect(k8sClient.Update(ctx, nbresource)).To(Succeed()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed()) + Expect(nbPolicy.DeletionTimestamp).NotTo(BeNil()) + }) + }) }) }) }) diff --git a/internal/controller/service_controller.go b/internal/controller/service_controller.go index 4bf1332..d5082ef 100644 --- a/internal/controller/service_controller.go +++ b/internal/controller/service_controller.go @@ -31,12 +31,14 @@ type ServiceReconciler struct { const ( // ServiceExposeAnnotation Service annotation for exposing - ServiceExposeAnnotation = "netbird.io/expose" - serviceGroupsAnnotation = "netbird.io/groups" - serviceResourceAnnotation = "netbird.io/resource-name" - servicePolicyAnnotation = "netbird.io/policy" - servicePortsAnnotation = "netbird.io/policy-ports" - serviceProtocolAnnotation = "netbird.io/policy-protocol" + ServiceExposeAnnotation = "netbird.io/expose" + serviceGroupsAnnotation = "netbird.io/groups" + serviceResourceAnnotation = "netbird.io/resource-name" + servicePolicyAnnotation = "netbird.io/policy" + servicePortsAnnotation = "netbird.io/policy-ports" + serviceProtocolAnnotation = "netbird.io/policy-protocol" + servicePolicySourceGroupsAnnotation = "netbird.io/policy-source-groups" + servicePolicyNameAnnotation = "netbird.io/policy-name" ) var ( @@ -241,6 +243,23 @@ func (r *ServiceReconciler) applyPolicy(nbResource *netbirdiov1.NBResource, svc } } + if v, ok := svc.Annotations[servicePolicySourceGroupsAnnotation]; ok { + nbResource.Spec.PolicySourceGroups = util.SplitTrim(v, ",") + } else { + nbResource.Spec.PolicySourceGroups = nil + } + + policyFriendlyNameList := util.SplitTrim(svc.Annotations[servicePolicyNameAnnotation], ",") + nbResource.Spec.PolicyFriendlyName = make(map[string]string) + for _, v := range policyFriendlyNameList { + friendlyNameMap := util.SplitTrim(v, ":") + if len(friendlyNameMap) != 2 { + logger.Info("Invalid number of : found in annotation", "annotation", servicePolicyNameAnnotation, "value", v) + continue + } + nbResource.Spec.PolicyFriendlyName[friendlyNameMap[0]] = friendlyNameMap[1] + } + for _, p := range svc.Spec.Ports { switch p.Protocol { case corev1.ProtocolTCP: diff --git a/internal/controller/service_controller_test.go b/internal/controller/service_controller_test.go index 945b02b..63a7098 100644 --- a/internal/controller/service_controller_test.go +++ b/internal/controller/service_controller_test.go @@ -440,6 +440,48 @@ var _ = Describe("Service Controller", func() { Expect(nbResource.Spec.UDPPorts).To(BeEmpty()) }) }) + When("policy friendly name changes", func() { + It("should update policy friendly name in NBResource spec", func() { + nbResource := &netbirdiov1.NBResource{} + Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed()) + nbResource.Spec.PolicyName = policyName + Expect(k8sClient.Update(ctx, nbResource)).To(Succeed()) + + service.Annotations[servicePolicyAnnotation] = policyName + service.Annotations[servicePolicyNameAnnotation] = "test:toast,meow:meow" + Expect(k8sClient.Update(ctx, service)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + nbResource = &netbirdiov1.NBResource{} + Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed()) + Expect(nbResource.Spec.PolicyFriendlyName).To(BeEquivalentTo(map[string]string{"test": "toast", "meow": "meow"})) + }) + }) + When("policy source groups changes", func() { + It("should update policy source groups in NBResource spec", func() { + nbResource := &netbirdiov1.NBResource{} + Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed()) + nbResource.Spec.PolicyName = policyName + Expect(k8sClient.Update(ctx, nbResource)).To(Succeed()) + + service.Annotations[servicePolicyAnnotation] = policyName + service.Annotations[servicePolicySourceGroupsAnnotation] = "test" + Expect(k8sClient.Update(ctx, service)).To(Succeed()) + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + nbResource = &netbirdiov1.NBResource{} + Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed()) + Expect(nbResource.Spec.PolicySourceGroups).To(BeEquivalentTo([]string{"test"})) + }) + }) When("resource name changes", func() { It("should update name in NBResource spec", func() { service.Annotations[serviceResourceAnnotation] = "meow"