From a77a2c2a8dfe29b3e5233e9c8bb9ce48bfd59d7d Mon Sep 17 00:00:00 2001 From: Rene Dekker Date: Fri, 16 Jan 2026 12:01:49 -0800 Subject: [PATCH 1/2] feat(recommended labels): Set recommended labels Set recommended labels as per https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ --- pkg/controller/ippool/pool_controller_test.go | 2 +- .../managed_cluster_controller.go | 2 +- .../logstorage/secrets/secret_controller.go | 2 +- .../logstorage/users/users_controller.go | 2 +- .../monitor/monitor_controller_test.go | 9 +- .../secrets/cluster_ca_controller.go | 2 +- pkg/controller/secrets/tenant_controller.go | 2 +- pkg/controller/utils/component.go | 110 +++++++++++++- pkg/controller/utils/component_test.go | 138 ++++++++++++++---- test/pool_test.go | 15 +- 10 files changed, 235 insertions(+), 49 deletions(-) diff --git a/pkg/controller/ippool/pool_controller_test.go b/pkg/controller/ippool/pool_controller_test.go index f393830ce2..fdd2faf840 100644 --- a/pkg/controller/ippool/pool_controller_test.go +++ b/pkg/controller/ippool/pool_controller_test.go @@ -255,7 +255,7 @@ var _ = Describe("IP Pool controller tests", func() { } for _, pool := range instance.Spec.CalicoNetwork.IPPools { Expect(poolsByCIDR).To(HaveKey(pool.CIDR)) - Expect(poolsByCIDR[pool.CIDR].Labels).To(Equal(map[string]string{"app.kubernetes.io/managed-by": "tigera-operator"})) + Expect(poolsByCIDR[pool.CIDR].Labels).To(HaveKeyWithValue("app.kubernetes.io/managed-by", "tigera-operator")) } }) diff --git a/pkg/controller/logstorage/managedcluster/managed_cluster_controller.go b/pkg/controller/logstorage/managedcluster/managed_cluster_controller.go index fe801f47c9..e273ea34e8 100644 --- a/pkg/controller/logstorage/managedcluster/managed_cluster_controller.go +++ b/pkg/controller/logstorage/managedcluster/managed_cluster_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/logstorage/secrets/secret_controller.go b/pkg/controller/logstorage/secrets/secret_controller.go index 259dda2d0c..83421f73c6 100644 --- a/pkg/controller/logstorage/secrets/secret_controller.go +++ b/pkg/controller/logstorage/secrets/secret_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/logstorage/users/users_controller.go b/pkg/controller/logstorage/users/users_controller.go index 2f6865d2fc..85ec4268e2 100644 --- a/pkg/controller/logstorage/users/users_controller.go +++ b/pkg/controller/logstorage/users/users_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/monitor/monitor_controller_test.go b/pkg/controller/monitor/monitor_controller_test.go index 650e318af5..9a19931e0a 100644 --- a/pkg/controller/monitor/monitor_controller_test.go +++ b/pkg/controller/monitor/monitor_controller_test.go @@ -260,7 +260,14 @@ var _ = Describe("Monitor controller tests", func() { Expect(serviceMonitor.Spec.Endpoints).To(HaveLen(1)) // Verify that the default settings are propagated. - Expect(serviceMonitor.Labels).To(Equal(map[string]string{render.AppLabelName: monitor.TigeraExternalPrometheus})) + Expect(serviceMonitor.Labels).To(Equal(map[string]string{ + render.AppLabelName: monitor.TigeraExternalPrometheus, + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "tigera-external-prometheus", + "app.kubernetes.io/part-of": "Calico", + "app.kubernetes.io/component": "Monitor.operator.tigera.io", + })) Expect(serviceMonitor.Spec.Endpoints[0]).To(Equal(monitoringv1.Endpoint{ Params: map[string][]string{"match[]": {"{__name__=~\".+\"}"}}, Port: "web", diff --git a/pkg/controller/secrets/cluster_ca_controller.go b/pkg/controller/secrets/cluster_ca_controller.go index cc88153259..408956a782 100644 --- a/pkg/controller/secrets/cluster_ca_controller.go +++ b/pkg/controller/secrets/cluster_ca_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/secrets/tenant_controller.go b/pkg/controller/secrets/tenant_controller.go index 95dd1da8d0..4dced2e779 100644 --- a/pkg/controller/secrets/tenant_controller.go +++ b/pkg/controller/secrets/tenant_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/utils/component.go b/pkg/controller/utils/component.go index 290f080459..f58393b782 100644 --- a/pkg/controller/utils/component.go +++ b/pkg/controller/utils/component.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "reflect" + "regexp" "slices" "strings" "sync" @@ -235,7 +236,7 @@ func (c *componentHandler) createOrUpdateObject(ctx context.Context, obj client. setProbeTimeouts(obj) // Make sure we have our standard selector and pod labels - setStandardSelectorAndLabels(obj) + setStandardSelectorAndLabels(obj, c.cr) if err := ensureTLSCiphers(ctx, obj, c.client); err != nil { return fmt.Errorf("failed to set TLS Ciphers: %w", err) @@ -960,16 +961,31 @@ func setProbeTimeouts(obj client.Object) { } } -// setStandardSelectorAndLabels will set the k8s-app and app.kubernetes.io/name Labels on the podTemplates +// setStandardSelectorAndLabels will set the recommended labels found at +// https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +// It will also set the k8s-app and app.kubernetes.io/name Labels on the podTemplates // for Deployments and Daemonsets. If there is no Selector specified a selector will also be added // that selects the k8s-app label. -func setStandardSelectorAndLabels(obj client.Object) { +func setStandardSelectorAndLabels(obj client.Object, customResource metav1.Object) { + if obj.GetLabels() == nil { + obj.SetLabels(make(map[string]string)) + } + if customResource != nil { + // We do not want to set these labels on objects without a CR. They are usually deliberately not getting an + // owner ref and are not controlled by our operator. + addNameLabel(obj, obj.GetName()) + addInstanceLabel(obj, customResource) + addComponentLabel(obj, customResource) + addPartOfLabel(obj) + addManagedByLabel(obj) + } + var podTemplate *v1.PodTemplateSpec var name string switch obj := obj.(type) { case *apps.Deployment: d := obj - name = d.Name + name = sanitizeLabel(d.Name) if d.Labels == nil { d.Labels = make(map[string]string) } @@ -985,7 +1001,7 @@ func setStandardSelectorAndLabels(obj client.Object) { podTemplate = &d.Spec.Template case *apps.DaemonSet: d := obj - name = d.Name + name = sanitizeLabel(d.Name) if d.Spec.Selector == nil { d.Spec.Selector = &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -994,6 +1010,22 @@ func setStandardSelectorAndLabels(obj client.Object) { } } podTemplate = &d.Spec.Template + case *monitoringv1.Prometheus: + d := obj + if d.Spec.PodMetadata == nil { + d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{} + } + for k, v := range d.Labels { + d.Spec.PodMetadata.Labels[k] = v + } + case *monitoringv1.Alertmanager: + d := obj + if d.Spec.PodMetadata == nil { + d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{} + } + for k, v := range d.Labels { + d.Spec.PodMetadata.Labels[k] = v + } default: return } @@ -1004,8 +1036,72 @@ func setStandardSelectorAndLabels(obj client.Object) { if podTemplate.Labels["k8s-app"] == "" { podTemplate.Labels["k8s-app"] = name } - if podTemplate.Labels["app.kubernetes.io/name"] == "" { - podTemplate.Labels["app.kubernetes.io/name"] = name + if customResource != nil { + // We do not want to set these labels on objects without a CR. They are usually deliberately not getting an + // owner ref and are not controlled by our operator. + addNameLabel(podTemplate, obj.GetName()) + addInstanceLabel(podTemplate, customResource) + addComponentLabel(podTemplate, customResource) + addPartOfLabel(podTemplate) + addManagedByLabel(podTemplate) + } + +} + +// sanitizeLabel cleans an input string to conform to the validation for labels. A valid label must be an empty string +// or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character and it +// is validated with regex '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?'. +func sanitizeLabel(input string) string { + sanitized := regexp.MustCompile(`[^a-zA-Z0-9_.-]`).ReplaceAllString(input, "_") + return strings.Trim(sanitized, "-_.") +} + +// addNameLabel sets the name of the application. +// For more on recommended labels see: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func addNameLabel(obj metav1.Object, name string) { + if obj.GetLabels()["app.kubernetes.io/name"] == "" { + obj.GetLabels()["app.kubernetes.io/name"] = sanitizeLabel(name) + } + if obj.GetLabels()["k8s-app"] == "" { + obj.GetLabels()["k8s-app"] = sanitizeLabel(name) + } +} + +// addInstanceLabel sets a unique name identifying the instance of an application. We use the name of the custom resource +// that owns this object. +// For more on recommended labels see: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func addInstanceLabel(obj metav1.Object, cr metav1.Object) { + if obj.GetLabels()["app.kubernetes.io/instance"] == "" && cr != nil { + obj.GetLabels()["app.kubernetes.io/instance"] = sanitizeLabel(cr.GetName()) + } +} + +// addComponentLabel sets the component within the architecture. We use the kind of the custom resource that owns this +// object. +// For more on recommended labels see: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func addComponentLabel(obj metav1.Object, cr metav1.Object) { + if obj.GetLabels()["app.kubernetes.io/component"] == "" && cr != nil { + owner, ok := cr.(runtime.Object) + if ok && owner.GetObjectKind() != nil && owner.GetObjectKind() != nil { + obj.GetLabels()["app.kubernetes.io/component"] = sanitizeLabel(owner.GetObjectKind().GroupVersionKind().GroupKind().String()) + + } + } +} + +// addPartOfLabel sets the name of a higher level application this one is part of. We use the product variant. +// For more on recommended labels see: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func addPartOfLabel(obj metav1.Object) { + if obj.GetLabels()["app.kubernetes.io/part-of"] == "" { + obj.GetLabels()["app.kubernetes.io/part-of"] = "Calico" + } +} + +// addManagedByLabel sets the tool being used to manage the operation of an application. +// For more on recommended labels see: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func addManagedByLabel(obj metav1.Object) { + if obj.GetLabels()["app.kubernetes.io/managed-by"] == "" { + obj.GetLabels()["app.kubernetes.io/managed-by"] = sanitizeLabel(common.OperatorName()) } } diff --git a/pkg/controller/utils/component_test.go b/pkg/controller/utils/component_test.go index 3643f94e78..0b499bb7c4 100644 --- a/pkg/controller/utils/component_test.go +++ b/pkg/controller/utils/component_test.go @@ -443,7 +443,13 @@ var _ = Describe("Component handler tests", func() { By("checking that the namespace is created and desired label is present") expectedLabels := map[string]string{ - fakeComponentLabelKey: fakeComponentLabelValue, + fakeComponentLabelKey: fakeComponentLabelValue, + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", + "app.kubernetes.io/name": "test-namespace", + "k8s-app": "test-namespace", + "app.kubernetes.io/component": "Manager.operator.tigera.io", } nsKey := client.ObjectKey{ Name: "test-namespace", @@ -492,8 +498,14 @@ var _ = Describe("Component handler tests", func() { By("retrieving the namespace and checking that both current and desired labels are still present") expectedLabels = map[string]string{ - "extra": "extra-value", - fakeComponentLabelKey: fakeComponentLabelValue, + "extra": "extra-value", + fakeComponentLabelKey: fakeComponentLabelValue, + "k8s-app": "test-namespace", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "test-namespace", + "app.kubernetes.io/part-of": "Calico", } ns = &corev1.Namespace{} err = c.Get(ctx, nsKey, ns) @@ -502,9 +514,15 @@ var _ = Describe("Component handler tests", func() { By("changing a desired label") labels = map[string]string{ - "extra": "extra-value", - "cattle-not-pets": "indeed", - fakeComponentLabelKey: "not-present", + "extra": "extra-value", + "cattle-not-pets": "indeed", + fakeComponentLabelKey: "not-present", + "app.kubernetes.io/part-of": "Calico", + "k8s-app": "test-namespace", + "app.kubernetes.io/name": "test-namespace", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/instance": "tigera-secure", } ns.Labels = labels err = c.Update(ctx, ns) @@ -512,9 +530,15 @@ var _ = Describe("Component handler tests", func() { By("checking that the namespace is updated with new modified label") expectedLabels = map[string]string{ - "cattle-not-pets": "indeed", - "extra": "extra-value", - fakeComponentLabelKey: "not-present", + "cattle-not-pets": "indeed", + "extra": "extra-value", + fakeComponentLabelKey: "not-present", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "test-namespace", + "app.kubernetes.io/part-of": "Calico", + "k8s-app": "test-namespace", } nsKey = client.ObjectKey{ Name: "test-namespace", @@ -544,9 +568,15 @@ var _ = Describe("Component handler tests", func() { By("retrieving the namespace and checking that desired label is reconciled, everything else is left as-is") expectedLabels = map[string]string{ - "cattle-not-pets": "indeed", - "extra": "extra-value", - fakeComponentLabelKey: fakeComponentLabelValue, + "cattle-not-pets": "indeed", + "extra": "extra-value", + fakeComponentLabelKey: fakeComponentLabelValue, + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "test-namespace", + "k8s-app": "test-namespace", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/part-of": "Calico", } ns = &corev1.Namespace{} err = c.Get(ctx, nsKey, ns) @@ -1227,7 +1257,13 @@ var _ = Describe("Component handler tests", func() { ObjectMeta: metav1.ObjectMeta{ Name: "my-service", Labels: map[string]string{ - "old": "should-be-preserved", + "old": "should-be-preserved", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "my-service", + "app.kubernetes.io/part-of": "Calico", + "k8s-app": "my-service", + "app.kubernetes.io/component": "Manager.operator.tigera.io", }, }, Spec: corev1.ServiceSpec{ @@ -1245,7 +1281,13 @@ var _ = Describe("Component handler tests", func() { Expect(c.Get(ctx, client.ObjectKey{Name: "my-service"}, svcWithIP)).NotTo(HaveOccurred()) Expect(svcWithIP.Spec.ClusterIP).To(Equal("10.96.0.1")) Expect(svcWithIP.Labels).To(Equal(map[string]string{ - "old": "should-be-preserved", + "old": "should-be-preserved", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "my-service", + "app.kubernetes.io/part-of": "Calico", + "k8s-app": "my-service", + "app.kubernetes.io/component": "Manager.operator.tigera.io", })) // Now pretend we're the new operator version, wanting to remove the cluster IP. @@ -1271,8 +1313,14 @@ var _ = Describe("Component handler tests", func() { Expect(c.Get(ctx, client.ObjectKey{Name: "my-service"}, svcNoIP)).NotTo(HaveOccurred()) Expect(svcNoIP.Spec.ClusterIP).To(Equal("None")) Expect(svcNoIP.Labels).To(Equal(map[string]string{ - "old": "should-be-preserved", - "new": "should-be-added", + "old": "should-be-preserved", + "new": "should-be-added", + "k8s-app": "my-service", + "app.kubernetes.io/name": "my-service", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", + "app.kubernetes.io/component": "Manager.operator.tigera.io", })) // The fake client resets the resource version to 1 on create. @@ -1285,9 +1333,15 @@ var _ = Describe("Component handler tests", func() { Expect(err).NotTo(HaveOccurred()) Expect(c.Get(ctx, client.ObjectKey{Name: "my-service"}, svcNoIP)).NotTo(HaveOccurred()) Expect(svcNoIP.Labels).To(Equal(map[string]string{ - "old": "should-be-preserved", - "new": "should-be-added", - "newer": "should-be-added", + "old": "should-be-preserved", + "new": "should-be-added", + "newer": "should-be-added", + "k8s-app": "my-service", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/name": "my-service", + "app.kubernetes.io/part-of": "Calico", })) Expect(svcNoIP.ObjectMeta.ResourceVersion).To(Equal("2"), "Expected update to rev ResourceVersion") @@ -1755,8 +1809,12 @@ var _ = Describe("Component handler tests", func() { By("checking that the daemonset is created and labels are added") expectedLabels := map[string]string{ - "k8s-app": "test-daemonset", - "app.kubernetes.io/name": "test-daemonset", + "k8s-app": "test-daemonset", + "app.kubernetes.io/name": "test-daemonset", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", } expectedSelector := metav1.LabelSelector{ MatchLabels: map[string]string{"k8s-app": "test-daemonset"}, @@ -1793,8 +1851,12 @@ var _ = Describe("Component handler tests", func() { Expect(err).To(BeNil()) expectedLabels := map[string]string{ - "k8s-app": "test-daemonset", - "app.kubernetes.io/name": "test-daemonset", + "k8s-app": "test-daemonset", + "app.kubernetes.io/name": "test-daemonset", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", } expectedSelector := metav1.LabelSelector{ MatchLabels: map[string]string{"preset-key": "preset-value"}, @@ -1826,8 +1888,12 @@ var _ = Describe("Component handler tests", func() { Expect(err).To(BeNil()) expectedLabels := map[string]string{ - "k8s-app": "test-deployment", - "app.kubernetes.io/name": "test-deployment", + "k8s-app": "test-deployment", + "app.kubernetes.io/name": "test-deployment", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", + "app.kubernetes.io/component": "Manager.operator.tigera.io", } expectedSelector := metav1.LabelSelector{ MatchLabels: map[string]string{"k8s-app": "test-deployment"}, @@ -1865,8 +1931,12 @@ var _ = Describe("Component handler tests", func() { Expect(err).To(BeNil()) expectedLabels := map[string]string{ - "k8s-app": "test-deployment", - "app.kubernetes.io/name": "test-deployment", + "k8s-app": "test-deployment", + "app.kubernetes.io/name": "test-deployment", + "app.kubernetes.io/component": "Manager.operator.tigera.io", + "app.kubernetes.io/instance": "tigera-secure", + "app.kubernetes.io/managed-by": "tigera-operator", + "app.kubernetes.io/part-of": "Calico", } expectedSelector := metav1.LabelSelector{ MatchLabels: map[string]string{"preset-key": "preset-value"}, @@ -1881,6 +1951,20 @@ var _ = Describe("Component handler tests", func() { Expect(d.Spec.Template.GetLabels()).To(Equal(expectedLabels)) Expect(*d.Spec.Selector).To(Equal(expectedSelector)) }) + DescribeTable("should sanitize common labels so that they pass regexp validation", func(in string) { + Expect(sanitizeLabel(in)).To(MatchRegexp(`(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?`)) + }, + Entry("Valid, should remain unchanged", "My-Test_String.123"), + Entry("Invalid start/end, should be trimmed", "__My-Test_String.123.."), + Entry("Invalid characters (spaces)", "String with spaces"), + Entry("Invalid characters", "special-chars!@#$%^&*"), + Entry("Invalid start/end", "-leading-and-trailing-"), + Entry("Invalid start/end (multiple)", "____-leading-and-trailing-____"), + Entry("Empty string, should remain empty", ""), + Entry("Invalid, should become empty", "."), + Entry("Valid single character", "a"), + Entry("Valid", "a-b_c.d"), + Entry("Valid", "1.2.3.4")) }) Context("services account updates should not result in removal of data", func() { It("preserves secrets and image pull secrets that were present before object updates", func() { diff --git a/test/pool_test.go b/test/pool_test.go index 642e5e975d..07152962d4 100644 --- a/test/pool_test.go +++ b/test/pool_test.go @@ -20,6 +20,7 @@ import ( "strings" "time" + "github.com/elastic/cloud-on-k8s/v2/pkg/utils/maps" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -189,8 +190,8 @@ var _ = Describe("IPPool FV tests", func() { Expect(ipPools.Items[0].Spec.Disabled).To(Equal(false)) Expect(ipPools.Items[0].Spec.BlockSize).To(Equal(26)) Expect(ipPools.Items[0].Spec.NodeSelector).To(Equal("all()")) - Expect(ipPools.Items[0].Labels).To(HaveLen(1)) - Expect(*ipPools.Items[0].Spec.AssignmentMode).To(Equal(v3.Automatic)) + Expect(ipPools.Items[0].Labels).To(HaveKeyWithValue("app.kubernetes.io/managed-by", "tigera-operator")) + Expect(ipPools.Items[0].Spec.AssignmentMode).To(Equal(operator.AssignmentModeAutomatic)) }) It("should assume ownership of legacy default IP pools", func() { @@ -251,7 +252,7 @@ var _ = Describe("IPPool FV tests", func() { Expect(len(ipPools.Items)).To(Equal(1), fmt.Sprintf("Expected 1 IP pool, but got: %+v", ipPools.Items)) // This proves the operator has not assumed control. - Expect(ipPools.Items[0].Labels).To(HaveLen(0)) + Expect(ipPools.Items[0].Labels).NotTo(HaveKey("app.kubernetes.io/managed-by")) // Now, install the API server. createAPIServer(c, mgr, shutdownContext, nil) @@ -268,14 +269,12 @@ var _ = Describe("IPPool FV tests", func() { if len(v3Pools.Items) != 1 { return fmt.Errorf("Expected 1 IP pool, but got: %+v", v3Pools.Items) } - if len(v3Pools.Items[0].Labels) != 1 { - return fmt.Errorf("Expected 1 label on IP pool, but got: %+v", v3Pools.Items[0].Labels) + if !maps.ContainsKeys(v3Pools.Items[0].Labels, "app.kubernetes.io/managed-by") { + return fmt.Errorf("Expected app.kubernetes.io/managed-by label, but got: %+v", v3Pools.Items[0].Labels) } return nil }, 5*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) - Expect(v3Pools.Items[0].Labels).To(HaveKey("app.kubernetes.io/managed-by")) - // Verify that the default IPv4 pool has been subsumed by the operator. Expect(v3Pools.Items[0].Name).To(Equal("default-ipv4-ippool")) Expect(v3Pools.Items[0].Spec.CIDR).To(Equal("192.168.0.0/24")) @@ -351,7 +350,7 @@ var _ = Describe("IPPool FV tests", func() { Expect(len(ipPools.Items)).To(Equal(1), fmt.Sprintf("Expected 1 IP pool, but got: %+v", ipPools.Items)) // This proves the operator has not assumed control. - Expect(ipPools.Items[0].Labels).To(HaveLen(0)) + Expect(ipPools.Items[0].Labels).NotTo(HaveKey("app.kubernetes.io/managed-by")) // Now, install the API server. createAPIServer(c, mgr, shutdownContext, nil) From 2cccd5fc562bc656febb7a48b0de2039248b55f6 Mon Sep 17 00:00:00 2001 From: Rene Dekker Date: Fri, 30 Jan 2026 15:22:38 -0800 Subject: [PATCH 2/2] feat(recommended labels): Set recommended labels Set recommended labels as per https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ --- .../monitor/monitor_controller_test.go | 15 +++---- pkg/controller/utils/component.go | 45 +++++++++---------- test/pool_test.go | 2 +- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/pkg/controller/monitor/monitor_controller_test.go b/pkg/controller/monitor/monitor_controller_test.go index 9a19931e0a..8e6e1f5d1b 100644 --- a/pkg/controller/monitor/monitor_controller_test.go +++ b/pkg/controller/monitor/monitor_controller_test.go @@ -260,14 +260,13 @@ var _ = Describe("Monitor controller tests", func() { Expect(serviceMonitor.Spec.Endpoints).To(HaveLen(1)) // Verify that the default settings are propagated. - Expect(serviceMonitor.Labels).To(Equal(map[string]string{ - render.AppLabelName: monitor.TigeraExternalPrometheus, - "app.kubernetes.io/instance": "tigera-secure", - "app.kubernetes.io/managed-by": "tigera-operator", - "app.kubernetes.io/name": "tigera-external-prometheus", - "app.kubernetes.io/part-of": "Calico", - "app.kubernetes.io/component": "Monitor.operator.tigera.io", - })) + Expect(serviceMonitor.Labels).Should(And( + HaveKeyWithValue(render.AppLabelName, monitor.TigeraExternalPrometheus), + HaveKeyWithValue("app.kubernetes.io/instance", "tigera-secure"), + HaveKeyWithValue("app.kubernetes.io/managed-by", "tigera-operator"), + HaveKeyWithValue("app.kubernetes.io/name", "tigera-external-prometheus"), + HaveKeyWithValue("app.kubernetes.io/part-of", "Calico"), + )) Expect(serviceMonitor.Spec.Endpoints[0]).To(Equal(monitoringv1.Endpoint{ Params: map[string][]string{"match[]": {"{__name__=~\".+\"}"}}, Port: "web", diff --git a/pkg/controller/utils/component.go b/pkg/controller/utils/component.go index f58393b782..407bfbd478 100644 --- a/pkg/controller/utils/component.go +++ b/pkg/controller/utils/component.go @@ -17,6 +17,7 @@ package utils import ( "context" "fmt" + "maps" "reflect" "regexp" "slices" @@ -1013,39 +1014,35 @@ func setStandardSelectorAndLabels(obj client.Object, customResource metav1.Objec case *monitoringv1.Prometheus: d := obj if d.Spec.PodMetadata == nil { - d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{} - } - for k, v := range d.Labels { - d.Spec.PodMetadata.Labels[k] = v + d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{Labels: make(map[string]string)} } + maps.Copy(d.Spec.PodMetadata.Labels, d.Labels) case *monitoringv1.Alertmanager: d := obj if d.Spec.PodMetadata == nil { - d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{} - } - for k, v := range d.Labels { - d.Spec.PodMetadata.Labels[k] = v + d.Spec.PodMetadata = &monitoringv1.EmbeddedObjectMetadata{Labels: make(map[string]string)} } + maps.Copy(d.Spec.PodMetadata.Labels, d.Labels) default: return } - - if podTemplate.Labels == nil { - podTemplate.Labels = make(map[string]string) - } - if podTemplate.Labels["k8s-app"] == "" { - podTemplate.Labels["k8s-app"] = name - } - if customResource != nil { - // We do not want to set these labels on objects without a CR. They are usually deliberately not getting an - // owner ref and are not controlled by our operator. - addNameLabel(podTemplate, obj.GetName()) - addInstanceLabel(podTemplate, customResource) - addComponentLabel(podTemplate, customResource) - addPartOfLabel(podTemplate) - addManagedByLabel(podTemplate) + if podTemplate != nil { + if podTemplate.Labels == nil { + podTemplate.Labels = make(map[string]string) + } + if podTemplate.Labels["k8s-app"] == "" { + podTemplate.Labels["k8s-app"] = name + } + if customResource != nil { + // We do not want to set these labels on objects without a CR. They are usually deliberately not getting an + // owner ref and are not controlled by our operator. + addNameLabel(podTemplate, obj.GetName()) + addInstanceLabel(podTemplate, customResource) + addComponentLabel(podTemplate, customResource) + addPartOfLabel(podTemplate) + addManagedByLabel(podTemplate) + } } - } // sanitizeLabel cleans an input string to conform to the validation for labels. A valid label must be an empty string diff --git a/test/pool_test.go b/test/pool_test.go index 07152962d4..693b7c442a 100644 --- a/test/pool_test.go +++ b/test/pool_test.go @@ -191,7 +191,7 @@ var _ = Describe("IPPool FV tests", func() { Expect(ipPools.Items[0].Spec.BlockSize).To(Equal(26)) Expect(ipPools.Items[0].Spec.NodeSelector).To(Equal("all()")) Expect(ipPools.Items[0].Labels).To(HaveKeyWithValue("app.kubernetes.io/managed-by", "tigera-operator")) - Expect(ipPools.Items[0].Spec.AssignmentMode).To(Equal(operator.AssignmentModeAutomatic)) + Expect(*ipPools.Items[0].Spec.AssignmentMode).To(Equal(v3.Automatic)) }) It("should assume ownership of legacy default IP pools", func() {