|
1 | 1 | package rabbitmq |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/json" |
4 | 5 | "fmt" |
5 | 6 |
|
6 | 7 | rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" |
| 8 | + "github.com/openstack-k8s-operators/lib-common/modules/common/service" |
| 9 | + "github.com/openstack-k8s-operators/lib-common/modules/common/util" |
7 | 10 | corev1 "k8s.io/api/core/v1" |
8 | 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
9 | 12 | "k8s.io/apimachinery/pkg/util/intstr" |
| 13 | + "k8s.io/apimachinery/pkg/util/strategicpatch" |
10 | 14 | "k8s.io/utils/ptr" |
11 | 15 | ) |
12 | 16 |
|
13 | | -// serviceOverrideType returns the service type from override, or empty string |
14 | | -func serviceOverrideType(r *rabbitmqv1.RabbitMq) corev1.ServiceType { |
15 | | - if r.Spec.Override.Service != nil && r.Spec.Override.Service.Spec != nil { |
16 | | - return r.Spec.Override.Service.Spec.Type |
17 | | - } |
18 | | - return "" |
19 | | -} |
| 17 | +// applyServiceOverride applies override metadata and spec fields to a Service. |
| 18 | +// The full corev1.ServiceSpec override is converted to lib-common's restricted |
| 19 | +// service.OverrideServiceSpec before merging via strategic merge patch, so only |
| 20 | +// the whitelisted fields are applied and unsafe fields like Selector, Ports, or |
| 21 | +// ClusterIP cannot be overridden. |
| 22 | +func applyServiceOverride(svc *corev1.Service, override *rabbitmqv1.RabbitMQServiceOverride) error { |
| 23 | + if override == nil { |
| 24 | + return nil |
| 25 | + } |
| 26 | + if override.EmbeddedLabelsAnnotations != nil { |
| 27 | + svc.Labels = util.MergeStringMaps(override.Labels, svc.Labels) |
| 28 | + svc.Annotations = util.MergeStringMaps(override.Annotations, svc.Annotations) |
| 29 | + } |
| 30 | + if override.Spec != nil { |
| 31 | + overrideSpecJSON, err := json.Marshal(override.Spec) |
| 32 | + if err != nil { |
| 33 | + return fmt.Errorf("error marshalling service override spec: %w", err) |
| 34 | + } |
| 35 | + whitelisted := &service.OverrideServiceSpec{} |
| 36 | + if err := json.Unmarshal(overrideSpecJSON, whitelisted); err != nil { |
| 37 | + return fmt.Errorf("error converting to OverrideServiceSpec: %w", err) |
| 38 | + } |
20 | 39 |
|
21 | | -// serviceOverrideIPFamilyPolicy returns the IPFamilyPolicy from override, or nil |
22 | | -func serviceOverrideIPFamilyPolicy(r *rabbitmqv1.RabbitMq) *corev1.IPFamilyPolicy { |
23 | | - if r.Spec.Override.Service != nil && r.Spec.Override.Service.Spec != nil { |
24 | | - return r.Spec.Override.Service.Spec.IPFamilyPolicy |
| 40 | + originalJSON, err := json.Marshal(svc.Spec) |
| 41 | + if err != nil { |
| 42 | + return fmt.Errorf("error marshalling service spec: %w", err) |
| 43 | + } |
| 44 | + patchJSON, err := json.Marshal(whitelisted) |
| 45 | + if err != nil { |
| 46 | + return fmt.Errorf("error marshalling override patch: %w", err) |
| 47 | + } |
| 48 | + patchedJSON, err := strategicpatch.StrategicMergePatch(originalJSON, patchJSON, corev1.ServiceSpec{}) |
| 49 | + if err != nil { |
| 50 | + return fmt.Errorf("error applying service spec override: %w", err) |
| 51 | + } |
| 52 | + if err := json.Unmarshal(patchedJSON, &svc.Spec); err != nil { |
| 53 | + return fmt.Errorf("error unmarshalling patched service spec: %w", err) |
| 54 | + } |
25 | 55 | } |
26 | 56 | return nil |
27 | 57 | } |
@@ -64,7 +94,7 @@ const ( |
64 | 94 |
|
65 | 95 | // HeadlessService creates the headless service for StatefulSet pod DNS |
66 | 96 | // matching the old rabbitmq-cluster-operator layout |
67 | | -func HeadlessService(r *rabbitmqv1.RabbitMq) *corev1.Service { |
| 97 | +func HeadlessService(r *rabbitmqv1.RabbitMq) (*corev1.Service, error) { |
68 | 98 | ls := CommonLabels(r.Name) |
69 | 99 | selector := SelectorLabels(r.Name) |
70 | 100 |
|
@@ -98,61 +128,57 @@ func HeadlessService(r *rabbitmqv1.RabbitMq) *corev1.Service { |
98 | 128 | }, |
99 | 129 | } |
100 | 130 |
|
101 | | - // Apply override settings if specified |
102 | | - if ipfp := serviceOverrideIPFamilyPolicy(r); ipfp != nil { |
103 | | - svc.Spec.IPFamilyPolicy = ipfp |
| 131 | + if override := r.Spec.Override.Service; override != nil { |
| 132 | + if override.EmbeddedLabelsAnnotations != nil { |
| 133 | + svc.Labels = util.MergeStringMaps(override.Labels, svc.Labels) |
| 134 | + svc.Annotations = util.MergeStringMaps(override.Annotations, svc.Annotations) |
| 135 | + } |
| 136 | + // Only IPFamilyPolicy is applicable to headless services — other spec |
| 137 | + // fields (Type, ExternalTrafficPolicy, LoadBalancerClass, etc.) are |
| 138 | + // either invalid or meaningless for ClusterIP/None services. |
| 139 | + if override.Spec != nil && override.Spec.IPFamilyPolicy != nil { |
| 140 | + svc.Spec.IPFamilyPolicy = override.Spec.IPFamilyPolicy |
| 141 | + } |
104 | 142 | } |
105 | 143 |
|
106 | | - return svc |
| 144 | + return svc, nil |
107 | 145 | } |
108 | 146 |
|
109 | 147 | // ClientService creates the client-facing service for RabbitMQ |
110 | 148 | // matching the old rabbitmq-cluster-operator layout |
111 | | -func ClientService(r *rabbitmqv1.RabbitMq) *corev1.Service { |
| 149 | +func ClientService(r *rabbitmqv1.RabbitMq) (*corev1.Service, error) { |
112 | 150 | ls := CommonLabels(r.Name) |
113 | 151 | selector := SelectorLabels(r.Name) |
114 | 152 |
|
115 | 153 | ports := buildClientServicePorts(r) |
116 | 154 |
|
117 | | - serviceType := corev1.ServiceTypeClusterIP |
118 | | - annotations := make(map[string]string) |
119 | | - |
120 | | - // Apply override settings if specified |
121 | | - if overrideType := serviceOverrideType(r); overrideType != "" { |
122 | | - serviceType = overrideType |
123 | | - } |
124 | | - if r.Spec.Override.Service != nil && r.Spec.Override.Service.EmbeddedLabelsAnnotations != nil { |
125 | | - for k, v := range r.Spec.Override.Service.Annotations { |
126 | | - annotations[k] = v |
127 | | - } |
128 | | - } |
129 | | - |
130 | | - if serviceType == corev1.ServiceTypeLoadBalancer { |
131 | | - if _, exists := annotations["dnsmasq.network.openstack.org/hostname"]; !exists { |
132 | | - annotations["dnsmasq.network.openstack.org/hostname"] = fmt.Sprintf("%s.%s.svc", r.Name, r.Namespace) |
133 | | - } |
134 | | - } |
135 | | - |
136 | 155 | svc := &corev1.Service{ |
137 | 156 | ObjectMeta: metav1.ObjectMeta{ |
138 | | - Name: r.Name, |
139 | | - Namespace: r.Namespace, |
140 | | - Labels: ls, |
141 | | - Annotations: annotations, |
| 157 | + Name: r.Name, |
| 158 | + Namespace: r.Namespace, |
| 159 | + Labels: ls, |
142 | 160 | }, |
143 | 161 | Spec: corev1.ServiceSpec{ |
144 | | - Type: serviceType, |
| 162 | + Type: corev1.ServiceTypeClusterIP, |
145 | 163 | Selector: selector, |
146 | 164 | Ports: ports, |
147 | 165 | }, |
148 | 166 | } |
149 | 167 |
|
150 | | - // Apply IPFamilyPolicy from override if specified |
151 | | - if ipfp := serviceOverrideIPFamilyPolicy(r); ipfp != nil { |
152 | | - svc.Spec.IPFamilyPolicy = ipfp |
| 168 | + if err := applyServiceOverride(svc, r.Spec.Override.Service); err != nil { |
| 169 | + return nil, fmt.Errorf("client service override: %w", err) |
| 170 | + } |
| 171 | + |
| 172 | + if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { |
| 173 | + if svc.Annotations == nil { |
| 174 | + svc.Annotations = make(map[string]string) |
| 175 | + } |
| 176 | + if _, exists := svc.Annotations["dnsmasq.network.openstack.org/hostname"]; !exists { |
| 177 | + svc.Annotations["dnsmasq.network.openstack.org/hostname"] = fmt.Sprintf("%s.%s.svc", r.Name, r.Namespace) |
| 178 | + } |
153 | 179 | } |
154 | 180 |
|
155 | | - return svc |
| 181 | + return svc, nil |
156 | 182 | } |
157 | 183 |
|
158 | 184 | // buildClientServicePorts builds the list of ports for the client service |
|
0 commit comments