Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion controller/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,15 @@ func Execute() {
zoneIDFilter,
cfg.CloudflareProxied,
cfg.DryRun,
cfg.CloudflareDNSRecordsPerPage,
cfg.CloudflareRegionKey,
cloudflare.CustomHostnamesConfig{
Enabled: cfg.CloudflareCustomHostnames,
MinTLSVersion: cfg.CloudflareCustomHostnamesMinTLSVersion,
CertificateAuthority: cfg.CloudflareCustomHostnamesCertificateAuthority,
},
cloudflare.DNSRecordsConfig{
PerPage: cfg.CloudflareDNSRecordsPerPage,
Comment: cfg.CloudflareDNSRecordsComment,
})
case "google":
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
Expand Down
1 change: 1 addition & 0 deletions docs/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
| `--cloudflare-custom-hostnames-certificate-authority=google` | When using the Cloudflare provider with the Custom Hostnames, specify which Cerrtificate Authority will be used by default. (default: google, options: google, ssl_com, lets_encrypt) |
| `--cloudflare-dns-records-per-page=100` | When using the Cloudflare provider, specify how many DNS records listed per page, max possible 5,000 (default: 100) |
| `--cloudflare-region-key=CLOUDFLARE-REGION-KEY` | When using the Cloudflare provider, specify the region (default: earth) |
| `--cloudflare-record-comment=""` | When using the Cloudflare provider, specify the comment for the DNS records (default: '') |
| `--coredns-prefix="/skydns/"` | When using the CoreDNS provider, specify the prefix name |
| `--akamai-serviceconsumerdomain=""` | When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified) |
| `--akamai-client-token=""` | When using the Akamai provider, specify the client token (required when --provider=akamai and edgerc-path not specified) |
Expand Down
2 changes: 2 additions & 0 deletions docs/tutorials/cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ spec:
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
env:
- name: CF_API_KEY
valueFrom:
Expand Down Expand Up @@ -205,6 +206,7 @@ spec:
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
env:
- name: CF_API_KEY
valueFrom:
Expand Down
6 changes: 5 additions & 1 deletion pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,12 @@ type Config struct {
AzureZonesCacheDuration time.Duration
CloudflareProxied bool
CloudflareCustomHostnames bool
CloudflareDNSRecordsPerPage int
CloudflareDNSRecordsComment string
CloudflareCustomHostnamesMinTLSVersion string
CloudflareCustomHostnamesCertificateAuthority string
CloudflareDNSRecordsPerPage int
CloudflareRegionKey string
CloudflareRecordComment string
CoreDNSPrefix string
AkamaiServiceConsumerDomain string
AkamaiClientToken string
Expand Down Expand Up @@ -536,6 +538,8 @@ func App(cfg *Config) *kingpin.Application {
app.Flag("cloudflare-custom-hostnames-certificate-authority", "When using the Cloudflare provider with the Custom Hostnames, specify which Cerrtificate Authority will be used by default. (default: google, options: google, ssl_com, lets_encrypt)").Default("google").EnumVar(&cfg.CloudflareCustomHostnamesCertificateAuthority, "google", "ssl_com", "lets_encrypt")
app.Flag("cloudflare-dns-records-per-page", "When using the Cloudflare provider, specify how many DNS records listed per page, max possible 5,000 (default: 100)").Default(strconv.Itoa(defaultConfig.CloudflareDNSRecordsPerPage)).IntVar(&cfg.CloudflareDNSRecordsPerPage)
app.Flag("cloudflare-region-key", "When using the Cloudflare provider, specify the region (default: earth)").StringVar(&cfg.CloudflareRegionKey)
app.Flag("cloudflare-record-comment", "When using the Cloudflare provider, specify the comment for the DNS records (default: '')").Default("").StringVar(&cfg.CloudflareRecordComment)

app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
app.Flag("akamai-serviceconsumerdomain", "When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiServiceConsumerDomain).StringVar(&cfg.AkamaiServiceConsumerDomain)
app.Flag("akamai-client-token", "When using the Akamai provider, specify the client token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiClientToken).StringVar(&cfg.AkamaiClientToken)
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
CloudflareCustomHostnamesMinTLSVersion: "1.0",
CloudflareCustomHostnamesCertificateAuthority: "google",
CloudflareDNSRecordsPerPage: 100,
CloudflareDNSRecordsComment: "",
CloudflareRegionKey: "",
CoreDNSPrefix: "/skydns/",
AkamaiServiceConsumerDomain: "",
Expand Down
71 changes: 66 additions & 5 deletions provider/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

cloudflare "github.com/cloudflare/cloudflare-go"
log "github.com/sirupsen/logrus"
"golang.org/x/net/publicsuffix"

"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
Expand All @@ -46,6 +47,10 @@ const (
cloudFlareUpdate = "UPDATE"
// defaultTTL 1 = automatic
defaultTTL = 1

// Cloudflare tier limitations https://developers.cloudflare.com/dns/manage-dns-records/reference/record-attributes/#availability
freeZoneMaxCommentLength = 100
paidZoneMaxCommentLength = 500
)

// We have to use pointers to bools now, as the upstream cloudflare-go library requires them
Expand Down Expand Up @@ -190,6 +195,45 @@ func (z zoneService) CreateCustomHostname(ctx context.Context, zoneID string, ch
return z.service.CreateCustomHostname(ctx, zoneID, ch)
}

type DNSRecordsConfig struct {
PerPage int
Comment string
}

func (c *DNSRecordsConfig) trimAndValidateComment(dnsName, comment string, paidZone func(string) bool) string {
if len(comment) > freeZoneMaxCommentLength {
if !paidZone(dnsName) {
log.Warnf("DNS record comment is invalid. Trimming comment of %s. To avoid endless syncs, please set it to less than %d chars.", dnsName, freeZoneMaxCommentLength)
return comment[:freeZoneMaxCommentLength]
} else if len(comment) > paidZoneMaxCommentLength {
log.Warnf("DNS record comment is invalid. Trimming comment of %s. To avoid endless syncs, please set it to less than %d chars.", dnsName, paidZoneMaxCommentLength)
return comment[:paidZoneMaxCommentLength]
}
}
Comment on lines +205 to +212
Copy link
Member

@ivankatliarchuk ivankatliarchuk May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional, as there are so many comments already. depends on readability/personal preference, but sometimes it's easier to read when there is a reduced nesting

example

if len(comment) <= freeZoneMaxCommentLength { return # early return }
if / else {}

which will be something like with early return

if len(comment) <= freeZoneMaxCommentLength {
	return comment
}

maxLength := freeZoneMaxCommentLength
if paidZone(dnsName) {
	maxLength = paidZoneMaxCommentLength
}

if len(comment) > maxLength {
	log.Warnf("DNS record comment is invalid. Trimming comment of %s. To avoid endless syncs, please set it to less than %d chars.", dnsName, maxLength)
	return comment[:maxLength]
}

return comment

return comment
}

func (p *CloudFlareProvider) ZoneHasPaidPlan(hostname string) bool {
zone, err := publicsuffix.EffectiveTLDPlusOne(hostname)
if err != nil {
log.Errorf("Failed to get effective TLD+1 for hostname %s %v", hostname, err)
return false
}
zoneID, err := p.Client.ZoneIDByName(zone)
if err != nil {
log.Errorf("Failed to get zone %s by name %v", zone, err)
return false
}

zoneDetails, err := p.Client.ZoneDetails(context.Background(), zoneID)
if err != nil {
log.Errorf("Failed to get zone %s details %v", zone, err)
return false
}

return zoneDetails.Plan.IsSubscribed
}

// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
type CloudFlareProvider struct {
provider.BaseProvider
Expand All @@ -198,9 +242,9 @@ type CloudFlareProvider struct {
domainFilter endpoint.DomainFilter
zoneIDFilter provider.ZoneIDFilter
proxiedByDefault bool
CustomHostnamesConfig CustomHostnamesConfig
DryRun bool
DNSRecordsPerPage int
CustomHostnamesConfig CustomHostnamesConfig
DNSRecordsConfig DNSRecordsConfig
RegionKey string
}

Expand Down Expand Up @@ -257,7 +301,7 @@ func getCreateDNSRecordParam(cfc cloudFlareChange) cloudflare.CreateDNSRecordPar
}

// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, dnsRecordsPerPage int, regionKey string, customHostnamesConfig CustomHostnamesConfig) (*CloudFlareProvider, error) {
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, regionKey string, customHostnamesConfig CustomHostnamesConfig, dnsRecordsConfig DNSRecordsConfig) (*CloudFlareProvider, error) {
// initialize via chosen auth method and returns new API object
var (
config *cloudflare.API
Expand Down Expand Up @@ -287,8 +331,8 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
proxiedByDefault: proxiedByDefault,
CustomHostnamesConfig: customHostnamesConfig,
DryRun: dryRun,
DNSRecordsPerPage: dnsRecordsPerPage,
RegionKey: regionKey,
DNSRecordsConfig: dnsRecordsConfig,
}, nil
}

Expand Down Expand Up @@ -825,6 +869,18 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, ep *endpoint.End
RegionKey: regionKey,
}
}

// Load comment from program flag
comment := p.DNSRecordsConfig.Comment
if val, ok := ep.GetProviderSpecificProperty(annotations.CloudflareRecordCommentKey); ok {
// Replace comment with Ingress annotation
comment = val
}

if len(comment) > freeZoneMaxCommentLength {
comment = p.DNSRecordsConfig.trimAndValidateComment(ep.DNSName, comment, p.ZoneHasPaidPlan)
}

return &cloudFlareChange{
Action: action,
ResourceRecord: cloudflare.DNSRecord{
Expand All @@ -835,6 +891,7 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, ep *endpoint.End
Proxied: &proxied,
Type: ep.RecordType,
Content: target,
Comment: comment,
},
RegionalHostname: regionalHostname,
CustomHostnamesPrev: prevCustomHostnames,
Expand All @@ -850,7 +907,7 @@ func newDNSRecordIndex(r cloudflare.DNSRecord) DNSRecordIndex {
func (p *CloudFlareProvider) listDNSRecordsWithAutoPagination(ctx context.Context, zoneID string) (DNSRecordsMap, error) {
// for faster getRecordID lookup
records := make(DNSRecordsMap)
resultInfo := cloudflare.ResultInfo{PerPage: p.DNSRecordsPerPage, Page: 1}
resultInfo := cloudflare.ResultInfo{PerPage: p.DNSRecordsConfig.PerPage, Page: 1}
params := cloudflare.ListDNSRecordsParams{ResultInfo: resultInfo}
for {
pageRecords, resultInfo, err := p.Client.ListDNSRecords(ctx, cloudflare.ZoneIdentifier(zoneID), params)
Expand Down Expand Up @@ -1021,6 +1078,10 @@ func groupByNameAndTypeWithCustomHostnames(records DNSRecordsMap, chs CustomHost
e = e.WithProviderSpecific(annotations.CloudflareCustomHostnameKey, strings.Join(customHostnames, ","))
}

if records[0].Comment != "" {
e = e.WithProviderSpecific(annotations.CloudflareRecordCommentKey, records[0].Comment)
}

endpoints = append(endpoints, e)
}

Expand Down
Loading
Loading