@@ -62,6 +62,8 @@ const (
6262 kubeCloudConfigName = "kube-cloud-config"
6363 // cloudCABundleKey is the key in the kube cloud config ConfigMap where the custom CA bundle is located
6464 cloudCABundleKey = "ca-bundle.pem"
65+ // dnsRecordIndexFieldName is the key for the DNSRecord index, used to identify conflicting domain names.
66+ dnsRecordIndexFieldName = "Spec.DNSName"
6567)
6668
6769var log = logf .Logger .WithName (controllerName )
@@ -81,6 +83,16 @@ func New(mgr manager.Manager, config Config) (runtimecontroller.Controller, erro
8183 if err := c .Watch (source .Kind [client.Object ](operatorCache , & iov1.DNSRecord {}, & handler.EnqueueRequestForObject {}, predicate.GenerationChangedPredicate {})); err != nil {
8284 return nil , err
8385 }
86+ // When a DNS record is deleted, there may be a conflicting record that should be published. Watch exclusively for
87+ // deletes, and queue a reconcile request for the appropriate conflicting record, if applicable.
88+ if err := c .Watch (source .Kind [client.Object ](operatorCache , & iov1.DNSRecord {}, handler .EnqueueRequestsFromMapFunc (reconciler .MapOnRecordDelete ), predicate.Funcs {
89+ CreateFunc : func (e event.CreateEvent ) bool { return false },
90+ DeleteFunc : func (e event.DeleteEvent ) bool { return true },
91+ UpdateFunc : func (e event.UpdateEvent ) bool { return false },
92+ GenericFunc : func (e event.GenericEvent ) bool { return false },
93+ })); err != nil {
94+ return nil , err
95+ }
8496 if err := c .Watch (source .Kind [client.Object ](operatorCache , & configv1.DNS {}, handler .EnqueueRequestsFromMapFunc (reconciler .ToDNSRecords ))); err != nil {
8597 return nil , err
8698 }
@@ -102,6 +114,12 @@ func New(mgr manager.Manager, config Config) (runtimecontroller.Controller, erro
102114 })); err != nil {
103115 return nil , err
104116 }
117+ if err := operatorCache .IndexField (context .Background (), & iov1.DNSRecord {}, dnsRecordIndexFieldName , func (o client.Object ) []string {
118+ dnsRecord := o .(* iov1.DNSRecord )
119+ return []string {dnsRecord .Spec .DNSName }
120+ }); err != nil {
121+ return nil , err
122+ }
105123 return c , nil
106124}
107125
@@ -341,8 +359,9 @@ func (r *reconciler) publishRecordToZones(zones []configv1.DNSZone, record *iov1
341359
342360 var err error
343361 var condition iov1.DNSZoneCondition
362+ var isDomainPublished bool
344363 if dnsPolicy == iov1 .UnmanagedDNS {
345- log .Info ("DNS record not published" , "record" , record .Spec )
364+ log .Info ("DNS record not published: DNS management policy is unmanaged " , "record" , record .Spec )
346365 condition = iov1.DNSZoneCondition {
347366 Message : "DNS record is currently not being managed by the operator" ,
348367 Reason : "UnmanagedDNS" ,
@@ -352,6 +371,24 @@ func (r *reconciler) publishRecordToZones(zones []configv1.DNSZone, record *iov1
352371 }
353372 } else if isRecordPublished {
354373 condition , err = r .replacePublishedRecord (zones [i ], record )
374+ } else if isDomainPublished , err = domainIsAlreadyPublishedToZone (context .Background (), r .cache , record , & zones [i ]); err != nil {
375+ log .Error (err , "failed to validate DNS record" , "record" , record .Spec )
376+ condition = iov1.DNSZoneCondition {
377+ Message : "Pre-publish validation failed" ,
378+ Reason : "InternalError" ,
379+ Status : string (operatorv1 .ConditionFalse ),
380+ Type : iov1 .DNSRecordPublishedConditionType ,
381+ LastTransitionTime : metav1 .Now (),
382+ }
383+ } else if isDomainPublished {
384+ log .Info ("DNS record not published: domain name already used by another DNS record" , "record" , record .Spec )
385+ condition = iov1.DNSZoneCondition {
386+ Message : "Domain name is already in use" ,
387+ Reason : "DomainAlreadyInUse" ,
388+ Status : string (operatorv1 .ConditionFalse ),
389+ Type : iov1 .DNSRecordPublishedConditionType ,
390+ LastTransitionTime : metav1 .Now (),
391+ }
355392 } else {
356393 condition , err = r .publishRecord (zones [i ], record )
357394 }
@@ -390,6 +427,35 @@ func recordIsAlreadyPublishedToZone(record *iov1.DNSRecord, zoneToPublish *confi
390427 return false
391428}
392429
430+ // domainIsAlreadyPublishedToZone returns true if the domain name in the provided DNSRecord is already published by
431+ // another existing dnsRecord.
432+ func domainIsAlreadyPublishedToZone (ctx context.Context , cache cache.Cache , record * iov1.DNSRecord , zone * configv1.DNSZone ) (bool , error ) {
433+ records := iov1.DNSRecordList {}
434+ if err := cache .List (ctx , & records , client.MatchingFields {dnsRecordIndexFieldName : record .Spec .DNSName }); err != nil {
435+ return false , err
436+ }
437+
438+ if len (records .Items ) == 0 {
439+ return false , nil
440+ }
441+
442+ for _ , existingRecord := range records .Items {
443+ // we only care if the domain name is published by a different record, so ignore the matching record if it
444+ // already exists.
445+ // TODO: There's got to be a better way to match the same object
446+ if (record .Name == existingRecord .Name ) && (record .Namespace == existingRecord .Namespace ) {
447+ continue
448+ }
449+ if record .Spec .DNSName != existingRecord .Spec .DNSName {
450+ continue
451+ }
452+ if recordIsAlreadyPublishedToZone (& existingRecord , zone ) {
453+ return true , nil
454+ }
455+ }
456+ return false , nil
457+ }
458+
393459func (r * reconciler ) delete (record * iov1.DNSRecord ) error {
394460 var errs []error
395461 for i := range record .Status .Zones {
@@ -580,6 +646,37 @@ func (r *reconciler) ToDNSRecords(ctx context.Context, o client.Object) []reconc
580646 return requests
581647}
582648
649+ func (r * reconciler ) MapOnRecordDelete (ctx context.Context , o client.Object ) []reconcile.Request {
650+ deletedRecord , ok := o .(* iov1.DNSRecord )
651+ if ! ok {
652+ log .Info ("failed to read DNS record" )
653+ return []reconcile.Request {}
654+ }
655+ otherRecords := iov1.DNSRecordList {}
656+ if err := r .cache .List (ctx , & otherRecords , client.MatchingFields {dnsRecordIndexFieldName : deletedRecord .Spec .DNSName }); err != nil {
657+ log .Error (err , "failed to list DNS records" )
658+ return []reconcile.Request {}
659+ }
660+ if len (otherRecords .Items ) == 0 {
661+ // Nothing to do.
662+ return []reconcile.Request {}
663+ }
664+ oldestExistingRecord := iov1.DNSRecord {}
665+ for _ , existingRecord := range otherRecords .Items {
666+ if oldestExistingRecord .CreationTimestamp .IsZero () || existingRecord .CreationTimestamp .Before (& oldestExistingRecord .CreationTimestamp ) {
667+ oldestExistingRecord = existingRecord
668+ }
669+ }
670+ return []reconcile.Request {
671+ reconcile.Request {
672+ NamespacedName : types.NamespacedName {
673+ Name : oldestExistingRecord .Name ,
674+ Namespace : oldestExistingRecord .Namespace ,
675+ },
676+ },
677+ }
678+ }
679+
583680// createDNSProvider creates a DNS manager compatible with the given cluster
584681// configuration.
585682func (r * reconciler ) createDNSProvider (dnsConfig * configv1.DNS , platformStatus * configv1.PlatformStatus , infraStatus * configv1.InfrastructureStatus , creds * corev1.Secret , AzureWorkloadIdentityEnabled bool ) (dns.Provider , error ) {
0 commit comments