diff --git a/cmd/main.go b/cmd/main.go index 5b5d535..cfdc68b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "strings" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -74,6 +75,7 @@ func main() { clusterDNS string netbirdAPIKey string allowAutomaticPolicyCreation bool + defaultLabels string ) 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") @@ -97,6 +99,12 @@ func main() { false, "Allow creating NBPolicy resources from annotations on Services", ) + flag.StringVar( + &defaultLabels, + "default-labels", + "", + "Default labels used for all resources, in format key=value,key=value", + ) // Controller generic flags var ( @@ -129,6 +137,17 @@ func main() { opts.BindFlags(flag.CommandLine) flag.Parse() + defaultLabelsMap := make(map[string]string) + if defaultLabels != "" { + for _, s := range strings.Split(defaultLabels, ",") { + kv := strings.Split(s, "=") + if len(kv) != 2 { + panic(fmt.Errorf("invalid label format: %s", s)) + } + defaultLabelsMap[kv[0]] = kv[1] + } + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) disableHTTP2 := func(c *tls.Config) { @@ -216,6 +235,7 @@ func main() { APIKey: netbirdAPIKey, ManagementURL: managementURL, NamespacedNetworks: namespacedNetworks, + DefaultLabels: defaultLabelsMap, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "NBRoutingPeer") os.Exit(1) @@ -234,6 +254,7 @@ func main() { ClusterDNS: clusterDNS, NamespacedNetworks: namespacedNetworks, ControllerNamespace: controllerNamespace, + DefaultLabels: defaultLabelsMap, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Service") os.Exit(1) @@ -246,6 +267,7 @@ func main() { ManagementURL: managementURL, AllowAutomaticPolicyCreation: allowAutomaticPolicyCreation, ClusterName: clusterName, + DefaultLabels: defaultLabelsMap, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "NBResource") os.Exit(1) diff --git a/helm/kubernetes-operator/templates/_helpers.tpl b/helm/kubernetes-operator/templates/_helpers.tpl index 2c98e59..ebef1ea 100644 --- a/helm/kubernetes-operator/templates/_helpers.tpl +++ b/helm/kubernetes-operator/templates/_helpers.tpl @@ -40,6 +40,11 @@ helm.sh/chart: {{ include "kubernetes-operator.chart" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.general.labels }} +{{- range $key, $val := .Values.general.labels }} +{{ $key }}: "{{ $val }}" +{{- end }} +{{- end }} {{- end }} {{/* diff --git a/helm/kubernetes-operator/templates/deployment.yaml b/helm/kubernetes-operator/templates/deployment.yaml index 462f11e..117b3eb 100644 --- a/helm/kubernetes-operator/templates/deployment.yaml +++ b/helm/kubernetes-operator/templates/deployment.yaml @@ -66,6 +66,13 @@ spec: {{- if .Values.routingClientImage }} - --netbird-client-image={{.Values.routingClientImage}} {{- end }} + {{- if .Values.general.labels }} + {{- $list := list }} + {{- range $k, $v := .Values.general.labels }} + {{- $list = append $list (printf "%s=%s" $k $v) }} + {{- end }} + - --default-labels="{{ join ", " $list }}" + {{- 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 6bcaa17..b02113a 100644 --- a/helm/kubernetes-operator/values.yaml +++ b/helm/kubernetes-operator/values.yaml @@ -198,3 +198,9 @@ netbirdAPI: {} # key: "NB_API_KEY" #routingClientImage: "netbirdio/netbird:latest" + +general: + # General labels, applied to all created K8s resources + labels: {} + # acme_com_managed_by: platform-engineering + # acme_com_owned_by: release-engineering \ No newline at end of file diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go index 21e4bbe..9d31586 100644 --- a/internal/controller/nbresource_controller.go +++ b/internal/controller/nbresource_controller.go @@ -31,6 +31,7 @@ type NBResourceReconciler struct { ManagementURL string AllowAutomaticPolicyCreation bool ClusterName string + DefaultLabels map[string]string netbird *netbird.Client } @@ -128,6 +129,7 @@ func (r *NBResourceReconciler) handlePolicyCreate(ctx context.Context, nbResourc 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, @@ -148,6 +150,7 @@ func (r *NBResourceReconciler) handlePolicyCreate(ctx context.Context, nbResourc 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, @@ -505,6 +508,7 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques }, }, Finalizers: []string{"netbird.io/group-cleanup", "netbird.io/resource-cleanup"}, + Labels: r.DefaultLabels, }, Spec: netbirdiov1.NBGroupSpec{ Name: groupName, diff --git a/internal/controller/nbresource_controller_test.go b/internal/controller/nbresource_controller_test.go index 3a7aa51..98cb115 100644 --- a/internal/controller/nbresource_controller_test.go +++ b/internal/controller/nbresource_controller_test.go @@ -46,10 +46,11 @@ var _ = Describe("NBResource Controller", func() { server = httptest.NewServer(mux) netbirdClient = netbird.New(server.URL, "ABC") controllerReconciler = &NBResourceReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - netbird: netbirdClient, - ClusterName: "kubernetes", + Client: k8sClient, + Scheme: k8sClient.Scheme(), + netbird: netbirdClient, + ClusterName: "kubernetes", + DefaultLabels: map[string]string{"dog": "bark"}, } By("creating the custom resource for the Kind NBResource") @@ -125,6 +126,7 @@ var _ = Describe("NBResource Controller", func() { Expect(err).NotTo(HaveOccurred()) nbGroup := &netbirdiov1.NBGroup{} Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "meow"}, nbGroup)).To(Succeed()) + Expect(nbGroup.Labels).To(HaveKeyWithValue("dog", "bark")) nbGroup.Status.GroupID = util.Ptr("test") Expect(k8sClient.Status().Update(ctx, nbGroup)).To(Succeed()) }) @@ -453,6 +455,7 @@ var _ = Describe("NBResource Controller", func() { 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.Labels).To(HaveKeyWithValue("dog", "bark")) }) When("Source groups is not defined", func() { diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go index 24d853f..5c8b697 100644 --- a/internal/controller/nbroutingpeer_controller.go +++ b/internal/controller/nbroutingpeer_controller.go @@ -31,6 +31,7 @@ type NBRoutingPeerReconciler struct { APIKey string ManagementURL string NamespacedNetworks bool + DefaultLabels map[string]string netbird *netbird.Client } @@ -122,6 +123,13 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl return err } + labels := r.DefaultLabels + for k, v := range nbrp.Spec.Labels { + labels[k] = v + } + podLabels := labels + podLabels["app.kubernetes.io/name"] = "netbird-router" + // Create deployment if errors.IsNotFound(err) { var replicas int32 = 3 @@ -141,7 +149,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl BlockOwnerDeletion: util.Ptr(true), }, }, - Labels: nbrp.Spec.Labels, + Labels: labels, Annotations: nbrp.Spec.Annotations, }, Spec: appsv1.DeploymentSpec{ @@ -153,9 +161,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl }, Template: corev1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ - Labels: map[string]string{ - "app.kubernetes.io/name": "netbird-router", - }, + Labels: podLabels, }, Spec: corev1.PodSpec{ NodeSelector: nbrp.Spec.NodeSelector, @@ -217,7 +223,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl BlockOwnerDeletion: util.Ptr(true), }, } - updatedDeployment.ObjectMeta.Labels = nbrp.Spec.Labels + updatedDeployment.ObjectMeta.Labels = labels for k, v := range nbrp.Spec.Annotations { updatedDeployment.ObjectMeta.Annotations[k] = nbrp.Spec.Annotations[v] } @@ -233,6 +239,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl } updatedDeployment.Spec.Template.Spec.Tolerations = nbrp.Spec.Tolerations updatedDeployment.Spec.Template.Spec.NodeSelector = nbrp.Spec.NodeSelector + updatedDeployment.Spec.Template.ObjectMeta.Labels = podLabels updatedDeployment.Spec.Template.Spec.Volumes = nbrp.Spec.Volumes updatedDeployment.Spec.Template.ObjectMeta.Labels = map[string]string{ "app.kubernetes.io/name": "netbird-router", @@ -378,6 +385,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R BlockOwnerDeletion: util.Ptr(true), }, }, + Labels: r.DefaultLabels, }, StringData: map[string]string{ "setupKey": setupKey.Key, @@ -402,7 +410,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R return &ctrl.Result{}, err } - if (err != nil && strings.Contains(err.Error(), "not found")) || setupKey.Revoked { + if err != nil || setupKey.Revoked { if setupKey != nil && setupKey.Revoked { err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) @@ -480,6 +488,7 @@ func (r *NBRoutingPeerReconciler) handleGroup(ctx context.Context, req ctrl.Requ }, }, Finalizers: []string{"netbird.io/group-cleanup", "netbird.io/routing-peer-cleanup"}, + Labels: r.DefaultLabels, }, Spec: netbirdiov1.NBGroupSpec{ Name: networkName, diff --git a/internal/controller/nbroutingpeer_controller_test.go b/internal/controller/nbroutingpeer_controller_test.go index af03663..b5ca41e 100644 --- a/internal/controller/nbroutingpeer_controller_test.go +++ b/internal/controller/nbroutingpeer_controller_test.go @@ -52,6 +52,7 @@ var _ = Describe("NBRoutingPeer Controller", func() { netbird: netbirdClient, ClientImage: "netbirdio/netbird:latest", ClusterName: "kubernetes", + DefaultLabels: make(map[string]string), NamespacedNetworks: false, } @@ -436,6 +437,7 @@ var _ = Describe("NBRoutingPeer Controller", func() { Expect(k8sClient.Create(ctx, secret)).To(Succeed()) }) It("should create group and requeue to get its ID", func() { + controllerReconciler.DefaultLabels = map[string]string{"dog": "bark"} res, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespacedName, }) @@ -445,6 +447,7 @@ var _ = Describe("NBRoutingPeer Controller", func() { group := &netbirdiov1.NBGroup{} Expect(k8sClient.Get(ctx, typeNamespacedName, group)).To(Succeed()) Expect(group.Spec.Name).To(Equal(controllerReconciler.ClusterName)) + Expect(group.Labels).To(HaveKeyWithValue("dog", "bark")) group.Status.GroupID = util.Ptr("test") Expect(k8sClient.Status().Update(ctx, group)).To(Succeed()) @@ -846,6 +849,25 @@ var _ = Describe("NBRoutingPeer Controller", func() { Expect(deployment.Spec.Template.Spec.Containers[0].Image).To(Equal(controllerReconciler.ClientImage)) }) }) + + When("Default labels exist", func() { + It("should add labels to Deployment and Pod metadata", func() { + controllerReconciler.DefaultLabels = map[string]string{ + "cat": "meow", + "dog": "bark", + } + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + + deployment := &appsv1.Deployment{} + Expect(k8sClient.Get(ctx, typeNamespacedName, deployment)).To(Succeed()) + Expect(deployment.Labels).To(HaveKeyWithValue("cat", "meow")) + Expect(deployment.Labels).To(HaveKeyWithValue("dog", "bark")) + }) + }) + When("Deployment is out-of-date", func() { It("should update deployment", func() { _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ diff --git a/internal/controller/service_controller.go b/internal/controller/service_controller.go index d5082ef..dd26e2d 100644 --- a/internal/controller/service_controller.go +++ b/internal/controller/service_controller.go @@ -27,6 +27,7 @@ type ServiceReconciler struct { ClusterDNS string NamespacedNetworks bool ControllerNamespace string + DefaultLabels map[string]string } const ( @@ -138,6 +139,7 @@ func (r *ServiceReconciler) exposeService(ctx context.Context, req ctrl.Request, Name: "router", Namespace: routerNamespace, Finalizers: []string{"netbird.io/cleanup"}, + Labels: r.DefaultLabels, }, Spec: netbirdiov1.NBRoutingPeerSpec{}, } @@ -205,6 +207,7 @@ func (r *ServiceReconciler) reconcileNBResource(nbResource *netbirdiov1.NBResour nbResource.ObjectMeta.Name = req.Name nbResource.ObjectMeta.Namespace = req.Namespace + nbResource.ObjectMeta.Labels = r.DefaultLabels nbResource.Finalizers = []string{"netbird.io/cleanup"} nbResource.Spec.Name = resourceName nbResource.Spec.NetworkID = *routingPeer.Status.NetworkID diff --git a/internal/controller/service_controller_test.go b/internal/controller/service_controller_test.go index 63a7098..6077c30 100644 --- a/internal/controller/service_controller_test.go +++ b/internal/controller/service_controller_test.go @@ -67,6 +67,7 @@ var _ = Describe("Service Controller", func() { NamespacedNetworks: false, ClusterDNS: "svc.cluster.local", ControllerNamespace: "default", + DefaultLabels: map[string]string{"dog": "bark"}, } }) @@ -144,6 +145,7 @@ var _ = Describe("Service Controller", func() { Expect(res.RequeueAfter).NotTo(BeZero()) nbrp := &netbirdiov1.NBRoutingPeer{} Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: typeNamespacedName.Namespace, Name: "router"}, nbrp)).To(Succeed()) + Expect(nbrp.Labels).To(HaveKeyWithValue("dog", "bark")) res, err = controllerReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: typeNamespacedName, }) @@ -203,6 +205,7 @@ var _ = Describe("Service Controller", func() { Expect(nbResource.Spec.PolicyName).To(BeEmpty()) Expect(nbResource.Spec.TCPPorts).To(BeEmpty()) Expect(nbResource.Spec.UDPPorts).To(BeEmpty()) + Expect(nbResource.Labels).To(HaveKeyWithValue("dog", "bark")) }) }) When("policy is specified", func() {