diff --git a/internal/controller/servicesync_controller.go b/internal/controller/servicesync_controller.go index 538bd48..be4e879 100644 --- a/internal/controller/servicesync_controller.go +++ b/internal/controller/servicesync_controller.go @@ -59,6 +59,10 @@ const ( // (and historical) prefix with this suffix, instead of inferring it from the // Service's currently assigned IP. AnnotationSuffix = "dynamic-prefix.io/suffix" + + // AnnotationSkipExternalDNSUpdate disables managed updates of the + // external-dns target annotation in HA mode. + AnnotationSkipExternalDNSUpdate = "dynamic-prefix.io/skip-external-dns-update" ) // ServiceSyncReconciler reconciles LoadBalancer Services for HA mode prefix transitions. @@ -167,7 +171,7 @@ func (r *ServiceSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) preservedTargets := extractUnmanagedIPs(existingTarget, managedPrefixes) finalTargets := append(preservedTargets, currentIP) finalTargetStr := strings.Join(finalTargets, ",") - if annotations[AnnotationExternalDNSTarget] != finalTargetStr { + if annotations[AnnotationSkipExternalDNSUpdate] != "true" && annotations[AnnotationExternalDNSTarget] != finalTargetStr { newAnnotations[AnnotationExternalDNSTarget] = finalTargetStr updated = true } diff --git a/internal/controller/servicesync_controller_test.go b/internal/controller/servicesync_controller_test.go index 83ef5c8..8cc088d 100644 --- a/internal/controller/servicesync_controller_test.go +++ b/internal/controller/servicesync_controller_test.go @@ -243,6 +243,26 @@ var _ = Describe("ServiceSync Controller", func() { Expect(ipsAnnotation).To(ContainSubstring("2001:db8:1::beef:42")) Expect(ipsAnnotation).To(ContainSubstring("2001:db8:2::beef:42")) }) + + It("should skip external-dns target updates when explicitly disabled", func() { + svc := &corev1.Service{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: serviceNS}, svc)).To(Succeed()) + + annotations := svc.GetAnnotations() + annotations[AnnotationSkipExternalDNSUpdate] = "true" + annotations[AnnotationExternalDNSTarget] = "existing.example.com" + svc.SetAnnotations(annotations) + Expect(k8sClient.Update(ctx, svc)).To(Succeed()) + + reconciler := &ServiceSyncReconciler{Client: k8sClient, Scheme: k8sClient.Scheme()} + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: serviceNS}}) + Expect(err).NotTo(HaveOccurred()) + + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: serviceNS}, svc)).To(Succeed()) + Expect(svc.GetAnnotations()[AnnotationExternalDNSTarget]).To(Equal("existing.example.com")) + Expect(svc.GetAnnotations()[AnnotationCiliumIPs]).To(ContainSubstring(currentIP)) + Expect(svc.GetAnnotations()[AnnotationCiliumIPs]).To(ContainSubstring(historicalIP)) + }) }) Context("When reconciling a Service in simple mode", func() { @@ -812,6 +832,11 @@ func TestServiceSyncAnnotationConstants(t *testing.T) { constant: AnnotationSuffix, expected: "dynamic-prefix.io/suffix", }, + { + name: "AnnotationSkipExternalDNSUpdate", + constant: AnnotationSkipExternalDNSUpdate, + expected: "dynamic-prefix.io/skip-external-dns-update", + }, } for _, tt := range tests {