diff --git a/api/v1beta1/rabbitmqcluster_types.go b/api/v1beta1/rabbitmqcluster_types.go index 855935293..19fd76c72 100644 --- a/api/v1beta1/rabbitmqcluster_types.go +++ b/api/v1beta1/rabbitmqcluster_types.go @@ -422,6 +422,8 @@ type RabbitmqClusterServiceSpec struct { // See also: https://pkg.go.dev/k8s.io/api/core/v1#IPFamilyPolicy // +kubebuilder:validation:Enum=SingleStack;PreferDualStack;RequireDualStack IPFamilyPolicy *corev1.IPFamilyPolicy `json:"ipFamilyPolicy,omitempty"` + // +optional + Labels map[string]string `json:"labels,omitempty"` } func (cluster *RabbitmqCluster) TLSEnabled() bool { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index e2318bed7..a855f7974 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -329,6 +329,13 @@ func (in *RabbitmqClusterServiceSpec) DeepCopyInto(out *RabbitmqClusterServiceSp *out = new(v1.IPFamilyPolicy) **out = **in } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *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 RabbitmqClusterServiceSpec. diff --git a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml index 393b656f4..13a9b836b 100644 --- a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml +++ b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml @@ -5155,6 +5155,10 @@ spec: - PreferDualStack - RequireDualStack type: string + labels: + additionalProperties: + type: string + type: object type: default: ClusterIP description: |- diff --git a/docs/api/rabbitmq.com.ref.asciidoc b/docs/api/rabbitmq.com.ref.asciidoc index 87132ffb8..ebb413968 100644 --- a/docs/api/rabbitmq.com.ref.asciidoc +++ b/docs/api/rabbitmq.com.ref.asciidoc @@ -347,6 +347,7 @@ For more info see https://pkg.go.dev/k8s.io/api/core/v1#ServiceType | *`annotations`* __object (keys:string, values:string)__ | Annotations to add to the Service. | *`ipFamilyPolicy`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#ipfamilypolicy-v1-core[$$IPFamilyPolicy$$]__ | IPFamilyPolicy represents the dual-stack-ness requested or required by a Service See also: https://pkg.go.dev/k8s.io/api/core/v1#IPFamilyPolicy +| *`labels`* __object (keys:string, values:string)__ | |=== diff --git a/internal/resource/service.go b/internal/resource/service.go index eaf720c77..5834aa222 100644 --- a/internal/resource/service.go +++ b/internal/resource/service.go @@ -56,6 +56,15 @@ func (builder *ServiceBuilder) Update(object client.Object) error { service := object.(*corev1.Service) builder.setAnnotations(service) service.Labels = metadata.GetLabels(builder.Instance.Name, builder.Instance.Labels) + + if builder.Instance.Spec.Service.Labels != nil { + for k, v := range builder.Instance.Spec.Service.Labels { + if _, exists := service.Labels[k]; !exists { + service.Labels[k] = v + } + } + } + service.Spec.Type = builder.Instance.Spec.Service.Type service.Spec.Selector = metadata.LabelSelector(builder.Instance.Name) service.Spec.IPFamilyPolicy = builder.Instance.Spec.Service.IPFamilyPolicy diff --git a/internal/resource/service_test.go b/internal/resource/service_test.go index c507c7bbc..e9ff7aa3a 100644 --- a/internal/resource/service_test.go +++ b/internal/resource/service_test.go @@ -478,6 +478,25 @@ var _ = Context("Services", func() { It("deletes the labels that are removed from the CR", func() { Expect(svc.Labels).NotTo(HaveKey("this-was-the-previous-label")) }) + + It("merges user provided service labels without overwriting internal labels", func() { + userServiceLabels := map[string]string{ + "user-label": "user-value", + "app.kubernetes.io/part-of": "should-not-overwrite", + } + + expectedLabels := map[string]string{ + "app.kubernetes.io/name": "foo", + "app.kubernetes.io/component": "rabbitmq", + "app.kubernetes.io/part-of": "rabbitmq", + "user-label": "user-value", + } + + service := updateServiceWithLabels(builder, nil, userServiceLabels) + + Expect(service.ObjectMeta.Labels).To(Equal(expectedLabels)) + }) + }) Context("Service Type", func() { @@ -902,3 +921,33 @@ func updateServiceWithAnnotations(rmqBuilder resource.RabbitmqResourceBuilder, i Expect(serviceBuilder.Update(svc)).To(Succeed()) return svc } + +func updateServiceWithLabels(rmqBuilder resource.RabbitmqResourceBuilder, instanceLabels, serviceLabels map[string]string) *corev1.Service { + instance := &rabbitmqv1beta1.RabbitmqCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo-namespace", + Labels: instanceLabels, + }, + Spec: rabbitmqv1beta1.RabbitmqClusterSpec{ + Service: rabbitmqv1beta1.RabbitmqClusterServiceSpec{ + Labels: serviceLabels, + }, + }, + } + + rmqBuilder.Instance = instance + serviceBuilder := rmqBuilder.Service() + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-service", + Namespace: "foo-namespace", + Labels: map[string]string{ + "app.kubernetes.io/name": "do-not-touch", + "app.kubernetes.io/part-of": "rabbitmq", + }, + }, + } + Expect(serviceBuilder.Update(svc)).To(Succeed()) + return svc +}