@@ -55,6 +55,22 @@ var proxyEnabled *bool = boolPtr(true)
55
55
// proxyDisabled is a pointer to a bool false showing the record should not be proxied through cloudflare
56
56
var proxyDisabled * bool = boolPtr (false )
57
57
58
+ // for faster getRecordID() lookup
59
+ type DNSRecordIndex struct {
60
+ Name string
61
+ Type string
62
+ Content string
63
+ }
64
+
65
+ type DNSRecordsMap map [DNSRecordIndex ]cloudflare.DNSRecord
66
+
67
+ // for faster getCustomHostname() lookup
68
+ type CustomHostnameIndex struct {
69
+ Hostname string
70
+ }
71
+
72
+ type CustomHostnamesMap map [CustomHostnameIndex ]cloudflare.CustomHostname
73
+
58
74
var recordTypeProxyNotSupported = map [string ]bool {
59
75
"LOC" : true ,
60
76
"MX" : true ,
@@ -229,7 +245,7 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
229
245
config , err = cloudflare .New (os .Getenv ("CF_API_KEY" ), os .Getenv ("CF_API_EMAIL" ))
230
246
}
231
247
if err != nil {
232
- return nil , fmt .Errorf ("failed to initialize cloudflare provider: %v " , err )
248
+ return nil , fmt .Errorf ("failed to initialize cloudflare provider: %w " , err )
233
249
}
234
250
provider := & CloudFlareProvider {
235
251
// Client: config,
@@ -254,10 +270,10 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
254
270
if len (p .zoneIDFilter .ZoneIDs ) > 0 && p .zoneIDFilter .ZoneIDs [0 ] != "" {
255
271
log .Debugln ("zoneIDFilter configured. only looking up zone IDs defined" )
256
272
for _ , zoneID := range p .zoneIDFilter .ZoneIDs {
257
- log .Debugf ("looking up zone %s " , zoneID )
273
+ log .Debugf ("looking up zone %q " , zoneID )
258
274
detailResponse , err := p .Client .ZoneDetails (ctx , zoneID )
259
275
if err != nil {
260
- log .Errorf ("zone %s lookup failed, %v" , zoneID , err )
276
+ log .Errorf ("zone %q lookup failed, %v" , zoneID , err )
261
277
return result , err
262
278
}
263
279
log .WithFields (log.Fields {
@@ -285,7 +301,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
285
301
286
302
for _ , zone := range zonesResponse .Result {
287
303
if ! p .domainFilter .Match (zone .Name ) {
288
- log .Debugf ("zone %s not in domain filter" , zone .Name )
304
+ log .Debugf ("zone %q not in domain filter" , zone .Name )
289
305
continue
290
306
}
291
307
result = append (result , zone )
@@ -359,6 +375,75 @@ func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Cha
359
375
return p .submitChanges (ctx , cloudflareChanges )
360
376
}
361
377
378
+ // submitCustomHostnameChanges implements Custom Hostname functionality for the Change, returns false if it fails
379
+ func (p * CloudFlareProvider ) submitCustomHostnameChanges (ctx context.Context , zoneID string , change * cloudFlareChange , chs CustomHostnamesMap , logFields log.Fields ) bool {
380
+ failedChange := false
381
+ // return early if disabled
382
+ if ! p .CustomHostnamesConfig .Enabled {
383
+ return ! failedChange
384
+ }
385
+
386
+ switch change .Action {
387
+ case cloudFlareUpdate :
388
+ if recordTypeCustomHostnameSupported [change .ResourceRecord .Type ] {
389
+ prevChName := change .CustomHostnamePrev
390
+ newChName := change .CustomHostname .Hostname
391
+ if prevCh , err := getCustomHostname (chs , prevChName ); err == nil {
392
+ prevChID := prevCh .ID
393
+ if prevChID != "" && prevChName != newChName {
394
+ log .WithFields (logFields ).Infof ("Removing previous custom hostname %q/%q" , prevChID , prevChName )
395
+ chErr := p .Client .DeleteCustomHostname (ctx , zoneID , prevChID )
396
+ if chErr != nil {
397
+ failedChange = true
398
+ log .WithFields (logFields ).Errorf ("failed to remove previous custom hostname %q/%q: %v" , prevChID , prevChName , chErr )
399
+ }
400
+ }
401
+ }
402
+ if newChName != "" && prevChName != newChName {
403
+ log .WithFields (logFields ).Infof ("Adding custom hostname %q" , newChName )
404
+ _ , chErr := p .Client .CreateCustomHostname (ctx , zoneID , change .CustomHostname )
405
+ if chErr != nil {
406
+ failedChange = true
407
+ log .WithFields (logFields ).Errorf ("failed to add custom hostname %q: %v" , newChName , chErr )
408
+ }
409
+ }
410
+ }
411
+ case cloudFlareDelete :
412
+ if recordTypeCustomHostnameSupported [change .ResourceRecord .Type ] && change .CustomHostname .Hostname != "" {
413
+ log .WithFields (logFields ).Infof ("Deleting custom hostname %q" , change .CustomHostname .Hostname )
414
+ if ch , err := getCustomHostname (chs , change .CustomHostname .Hostname ); err == nil {
415
+ chID := ch .ID
416
+ chErr := p .Client .DeleteCustomHostname (ctx , zoneID , chID )
417
+ if chErr != nil {
418
+ failedChange = true
419
+ log .WithFields (logFields ).Errorf ("failed to delete custom hostname %q/%q: %v" , chID , change .CustomHostname .Hostname , chErr )
420
+ }
421
+ } else {
422
+ log .WithFields (logFields ).Warnf ("failed to delete custom hostname %q: %v" , change .CustomHostname .Hostname , err )
423
+ }
424
+ }
425
+ case cloudFlareCreate :
426
+ if recordTypeCustomHostnameSupported [change .ResourceRecord .Type ] && change .CustomHostname .Hostname != "" {
427
+ log .WithFields (logFields ).Infof ("Creating custom hostname %q" , change .CustomHostname .Hostname )
428
+ if ch , err := getCustomHostname (chs , change .CustomHostname .Hostname ); err == nil {
429
+ if change .CustomHostname .CustomOriginServer == ch .CustomOriginServer {
430
+ log .WithFields (logFields ).Warnf ("custom hostname %q already exists with the same origin %q, continue" , change .CustomHostname .Hostname , ch .CustomOriginServer )
431
+ } else {
432
+ failedChange = true
433
+ log .WithFields (logFields ).Errorf ("failed to create custom hostname, %q already exists with origin %q" , change .CustomHostname .Hostname , ch .CustomOriginServer )
434
+ }
435
+ } else {
436
+ _ , chErr := p .Client .CreateCustomHostname (ctx , zoneID , change .CustomHostname )
437
+ if chErr != nil {
438
+ failedChange = true
439
+ log .WithFields (logFields ).Errorf ("failed to create custom hostname %q: %v" , change .CustomHostname .Hostname , chErr )
440
+ }
441
+ }
442
+ }
443
+ }
444
+ return ! failedChange
445
+ }
446
+
362
447
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
363
448
func (p * CloudFlareProvider ) submitChanges (ctx context.Context , changes []* cloudFlareChange ) error {
364
449
// return early if there is nothing to change
@@ -395,37 +480,15 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
395
480
resourceContainer := cloudflare .ZoneIdentifier (zoneID )
396
481
records , err := p .listDNSRecordsWithAutoPagination (ctx , zoneID )
397
482
if err != nil {
398
- return fmt .Errorf ("could not fetch records from zone, %v " , err )
483
+ return fmt .Errorf ("could not fetch records from zone, %w " , err )
399
484
}
400
485
chs , chErr := p .listCustomHostnamesWithPagination (ctx , zoneID )
401
486
if chErr != nil {
402
487
return fmt .Errorf ("could not fetch custom hostnames from zone, %v" , chErr )
403
488
}
404
489
if change .Action == cloudFlareUpdate {
405
- if recordTypeCustomHostnameSupported [change .ResourceRecord .Type ] {
406
- prevCh := change .CustomHostnamePrev
407
- newCh := change .CustomHostname .Hostname
408
- if prevCh != "" {
409
- prevChID , _ := p .getCustomHostnameOrigin (chs , prevCh )
410
- if prevChID != "" && prevCh != newCh {
411
- log .WithFields (logFields ).Infof ("Removing previous custom hostname %v/%v" , prevChID , prevCh )
412
- chErr := p .Client .DeleteCustomHostname (ctx , zoneID , prevChID )
413
- if chErr != nil {
414
- failedChange = true
415
- log .WithFields (logFields ).Errorf ("failed to remove previous custom hostname %v/%v: %v" , prevChID , prevCh , chErr )
416
- }
417
- }
418
- }
419
- if newCh != "" {
420
- if prevCh != newCh {
421
- log .WithFields (logFields ).Infof ("Adding custom hostname %v" , newCh )
422
- _ , chErr := p .Client .CreateCustomHostname (ctx , zoneID , change .CustomHostname )
423
- if chErr != nil {
424
- failedChange = true
425
- log .WithFields (logFields ).Errorf ("failed to add custom hostname %v: %v" , newCh , chErr )
426
- }
427
- }
428
- }
490
+ if ! p .submitCustomHostnameChanges (ctx , zoneID , change , chs , logFields ) {
491
+ failedChange = true
429
492
}
430
493
recordID := p .getRecordID (records , change .ResourceRecord )
431
494
if recordID == "" {
@@ -457,19 +520,8 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
457
520
failedChange = true
458
521
log .WithFields (logFields ).Errorf ("failed to delete record: %v" , err )
459
522
}
460
- if change .CustomHostname .Hostname == "" {
461
- continue
462
- }
463
- log .WithFields (logFields ).Infof ("Deleting custom hostname %v" , change .CustomHostname .Hostname )
464
- chID , _ := p .getCustomHostnameOrigin (chs , change .CustomHostname .Hostname )
465
- if chID == "" {
466
- log .WithFields (logFields ).Infof ("Custom hostname %v not found" , change .CustomHostname .Hostname )
467
- continue
468
- }
469
- chErr := p .Client .DeleteCustomHostname (ctx , zoneID , chID )
470
- if chErr != nil {
523
+ if ! p .submitCustomHostnameChanges (ctx , zoneID , change , chs , logFields ) {
471
524
failedChange = true
472
- log .WithFields (logFields ).Errorf ("failed to delete custom hostname %v/%v: %v" , chID , change .CustomHostname .Hostname , chErr )
473
525
}
474
526
} else if change .Action == cloudFlareCreate {
475
527
recordParam := getCreateDNSRecordParam (* change )
@@ -478,20 +530,8 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
478
530
failedChange = true
479
531
log .WithFields (logFields ).Errorf ("failed to create record: %v" , err )
480
532
}
481
- if change .CustomHostname .Hostname == "" {
482
- continue
483
- }
484
- log .WithFields (logFields ).Infof ("Creating custom hostname %v" , change .CustomHostname .Hostname )
485
- chID , chOrigin := p .getCustomHostnameOrigin (chs , change .CustomHostname .Hostname )
486
- if chID != "" {
533
+ if ! p .submitCustomHostnameChanges (ctx , zoneID , change , chs , logFields ) {
487
534
failedChange = true
488
- log .WithFields (logFields ).Errorf ("failed to create custom hostname, %v already exists for origin %v" , change .CustomHostname .Hostname , chOrigin )
489
- continue
490
- }
491
- _ , chErr := p .Client .CreateCustomHostname (ctx , zoneID , change .CustomHostname )
492
- if chErr != nil {
493
- failedChange = true
494
- log .WithFields (logFields ).Errorf ("failed to create custom hostname %v: %v" , change .CustomHostname .Hostname , chErr )
495
535
}
496
536
}
497
537
}
@@ -501,7 +541,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
501
541
}
502
542
503
543
if len (failedZones ) > 0 {
504
- return fmt .Errorf ("failed to submit all changes for the following zones: %v " , failedZones )
544
+ return fmt .Errorf ("failed to submit all changes for the following zones: %q " , failedZones )
505
545
}
506
546
507
547
return nil
@@ -535,7 +575,7 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
535
575
for _ , c := range changeSet {
536
576
zoneID , _ := zoneNameIDMapper .FindZone (c .ResourceRecord .Name )
537
577
if zoneID == "" {
538
- log .Debugf ("Skipping record %s because no hosted zone matching record DNS Name was detected" , c .ResourceRecord .Name )
578
+ log .Debugf ("Skipping record %q because no hosted zone matching record DNS Name was detected" , c .ResourceRecord .Name )
539
579
continue
540
580
}
541
581
changes [zoneID ] = append (changes [zoneID ], c )
@@ -544,22 +584,21 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
544
584
return changes
545
585
}
546
586
547
- func (p * CloudFlareProvider ) getRecordID (records []cloudflare.DNSRecord , record cloudflare.DNSRecord ) string {
548
- for _ , zoneRecord := range records {
549
- if zoneRecord .Name == record .Name && zoneRecord .Type == record .Type && zoneRecord .Content == record .Content {
550
- return zoneRecord .ID
551
- }
587
+ func (p * CloudFlareProvider ) getRecordID (records DNSRecordsMap , record cloudflare.DNSRecord ) string {
588
+ if zoneRecord , ok := records [DNSRecordIndex {Name : record .Name , Type : record .Type , Content : record .Content }]; ok {
589
+ return zoneRecord .ID
552
590
}
553
591
return ""
554
592
}
555
593
556
- func (p * CloudFlareProvider ) getCustomHostnameOrigin (chs []cloudflare.CustomHostname , hostname string ) (string , string ) {
557
- for _ , zoneCh := range chs {
558
- if zoneCh .Hostname == hostname {
559
- return zoneCh .ID , zoneCh .CustomOriginServer
560
- }
594
+ func getCustomHostname (chs CustomHostnamesMap , chName string ) (cloudflare.CustomHostname , error ) {
595
+ if chName == "" {
596
+ return cloudflare.CustomHostname {}, fmt .Errorf ("failed to get custom hostname: %q is empty" , chName )
597
+ }
598
+ if ch , ok := chs [CustomHostnameIndex {Hostname : chName }]; ok {
599
+ return ch , nil
561
600
}
562
- return "" , ""
601
+ return cloudflare. CustomHostname {}, fmt . Errorf ( "failed to get custom hostname: %q not found" , chName )
563
602
}
564
603
565
604
func (p * CloudFlareProvider ) newCloudFlareChange (action string , endpoint * endpoint.Endpoint , target string , current * endpoint.Endpoint ) * cloudFlareChange {
@@ -580,7 +619,7 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoi
580
619
newCustomHostname = cloudflare.CustomHostname {
581
620
Hostname : getEndpointCustomHostname (endpoint ),
582
621
CustomOriginServer : endpoint .DNSName ,
583
- SSL : getCustomHostnamesSSLOptions (endpoint , p .CustomHostnamesConfig ),
622
+ SSL : getCustomHostnamesSSLOptions (p .CustomHostnamesConfig ),
584
623
}
585
624
}
586
625
return & cloudFlareChange {
@@ -607,9 +646,14 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoi
607
646
}
608
647
}
609
648
649
+ func newDNSRecordIndex (r cloudflare.DNSRecord ) DNSRecordIndex {
650
+ return DNSRecordIndex {Name : r .Name , Type : r .Type , Content : r .Content }
651
+ }
652
+
610
653
// listDNSRecordsWithAutoPagination performs automatic pagination of results on requests to cloudflare.ListDNSRecords with custom per_page values
611
- func (p * CloudFlareProvider ) listDNSRecordsWithAutoPagination (ctx context.Context , zoneID string ) ([]cloudflare.DNSRecord , error ) {
612
- var records []cloudflare.DNSRecord
654
+ func (p * CloudFlareProvider ) listDNSRecordsWithAutoPagination (ctx context.Context , zoneID string ) (DNSRecordsMap , error ) {
655
+ // for faster getRecordID lookup
656
+ records := make (DNSRecordsMap )
613
657
resultInfo := cloudflare.ResultInfo {PerPage : p .DNSRecordsPerPage , Page : 1 }
614
658
params := cloudflare.ListDNSRecordsParams {ResultInfo : resultInfo }
615
659
for {
@@ -625,7 +669,9 @@ func (p *CloudFlareProvider) listDNSRecordsWithAutoPagination(ctx context.Contex
625
669
return nil , err
626
670
}
627
671
628
- records = append (records , pageRecords ... )
672
+ for _ , r := range pageRecords {
673
+ records [newDNSRecordIndex (r )] = r
674
+ }
629
675
params .ResultInfo = resultInfo .Next ()
630
676
if params .ResultInfo .Done () {
631
677
break
@@ -634,12 +680,16 @@ func (p *CloudFlareProvider) listDNSRecordsWithAutoPagination(ctx context.Contex
634
680
return records , nil
635
681
}
636
682
683
+ func newCustomHostnameIndex (ch cloudflare.CustomHostname ) CustomHostnameIndex {
684
+ return CustomHostnameIndex {Hostname : ch .Hostname }
685
+ }
686
+
637
687
// listCustomHostnamesWithPagination performs automatic pagination of results on requests to cloudflare.CustomHostnames
638
- func (p * CloudFlareProvider ) listCustomHostnamesWithPagination (ctx context.Context , zoneID string ) ([]cloudflare. CustomHostname , error ) {
688
+ func (p * CloudFlareProvider ) listCustomHostnamesWithPagination (ctx context.Context , zoneID string ) (CustomHostnamesMap , error ) {
639
689
if ! p .CustomHostnamesConfig .Enabled {
640
690
return nil , nil
641
691
}
642
- var chs []cloudflare. CustomHostname
692
+ chs := make ( CustomHostnamesMap )
643
693
resultInfo := cloudflare.ResultInfo {Page : 1 }
644
694
for {
645
695
pageCustomHostnameListResponse , result , err := p .Client .CustomHostnames (ctx , zoneID , resultInfo .Page , cloudflare.CustomHostname {})
@@ -651,11 +701,12 @@ func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx context.Conte
651
701
return nil , provider .NewSoftError (err )
652
702
}
653
703
}
654
- log .Errorf ("zone %s failed to fetch custom hostnames. Please check if \" Cloudflare for SaaS\" is enabled and API key permissions, %v" , zoneID , err )
704
+ log .Errorf ("zone %q failed to fetch custom hostnames. Please check if \" Cloudflare for SaaS\" is enabled and API key permissions, %v" , zoneID , err )
655
705
return nil , err
656
706
}
657
-
658
- chs = append (chs , pageCustomHostnameListResponse ... )
707
+ for _ , ch := range pageCustomHostnameListResponse {
708
+ chs [newCustomHostnameIndex (ch )] = ch
709
+ }
659
710
resultInfo = result .Next ()
660
711
if resultInfo .Done () {
661
712
break
@@ -664,7 +715,7 @@ func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx context.Conte
664
715
return chs , nil
665
716
}
666
717
667
- func getCustomHostnamesSSLOptions (endpoint * endpoint. Endpoint , customHostnamesConfig CustomHostnamesConfig ) * cloudflare.CustomHostnameSSL {
718
+ func getCustomHostnamesSSLOptions (customHostnamesConfig CustomHostnamesConfig ) * cloudflare.CustomHostnameSSL {
668
719
return & cloudflare.CustomHostnameSSL {
669
720
Type : "dv" ,
670
721
Method : "http" ,
@@ -683,7 +734,7 @@ func shouldBeProxied(endpoint *endpoint.Endpoint, proxiedByDefault bool) bool {
683
734
if v .Name == source .CloudflareProxiedKey {
684
735
b , err := strconv .ParseBool (v .Value )
685
736
if err != nil {
686
- log .Errorf ("Failed to parse annotation [%s ]: %v" , source .CloudflareProxiedKey , err )
737
+ log .Errorf ("Failed to parse annotation [%q ]: %v" , source .CloudflareProxiedKey , err )
687
738
} else {
688
739
proxied = b
689
740
}
@@ -706,7 +757,7 @@ func getEndpointCustomHostname(endpoint *endpoint.Endpoint) string {
706
757
return ""
707
758
}
708
759
709
- func groupByNameAndTypeWithCustomHostnames (records []cloudflare. DNSRecord , chs []cloudflare. CustomHostname ) []* endpoint.Endpoint {
760
+ func groupByNameAndTypeWithCustomHostnames (records DNSRecordsMap , chs CustomHostnamesMap ) []* endpoint.Endpoint {
710
761
endpoints := []* endpoint.Endpoint {}
711
762
712
763
// group supported records by name and type
0 commit comments