mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +02:00
improve cloudflare regional hostname implementation (#5309)
- add flag to enable regional hostname feature - support deletion of regional hostname on annotation edit - correctly support differences detection with cloudflare state - increased tests coverage Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
This commit is contained in:
parent
104157f8b1
commit
7108979df1
@ -215,7 +215,10 @@ func buildProvider(
|
|||||||
zoneIDFilter,
|
zoneIDFilter,
|
||||||
cfg.CloudflareProxied,
|
cfg.CloudflareProxied,
|
||||||
cfg.DryRun,
|
cfg.DryRun,
|
||||||
cfg.CloudflareRegionKey,
|
cloudflare.RegionalServicesConfig{
|
||||||
|
Enabled: cfg.CloudflareRegionalServices,
|
||||||
|
RegionKey: cfg.CloudflareRegionKey,
|
||||||
|
},
|
||||||
cloudflare.CustomHostnamesConfig{
|
cloudflare.CustomHostnamesConfig{
|
||||||
Enabled: cfg.CloudflareCustomHostnames,
|
Enabled: cfg.CloudflareCustomHostnames,
|
||||||
MinTLSVersion: cfg.CloudflareCustomHostnamesMinTLSVersion,
|
MinTLSVersion: cfg.CloudflareCustomHostnamesMinTLSVersion,
|
||||||
|
@ -93,7 +93,8 @@
|
|||||||
| `--cloudflare-custom-hostnames-min-tls-version=1.0` | When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3) |
|
| `--cloudflare-custom-hostnames-min-tls-version=1.0` | When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3) |
|
||||||
| `--cloudflare-custom-hostnames-certificate-authority=none` | When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none) |
|
| `--cloudflare-custom-hostnames-certificate-authority=none` | When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none) |
|
||||||
| `--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-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) |
|
| `--[no-]cloudflare-regional-services` | When using the Cloudflare provider, specify if Regional Services feature will be used (default: disabled) |
|
||||||
|
| `--cloudflare-region-key=CLOUDFLARE-REGION-KEY` | When using the Cloudflare provider, specify the default region for Regional Services. Any value other than an empty string will enable the Regional Services feature (optional) |
|
||||||
| `--cloudflare-record-comment=""` | When using the Cloudflare provider, specify the comment for the DNS records (default: '') |
|
| `--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 |
|
| `--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-serviceconsumerdomain=""` | When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified) |
|
||||||
|
@ -128,6 +128,7 @@ spec:
|
|||||||
- --provider=cloudflare
|
- --provider=cloudflare
|
||||||
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
|
- --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-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
|
||||||
|
- --cloudflare-regional-services # (optional) enable the regional hostname feature that configure which region can decrypt HTTPS requests
|
||||||
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
|
- --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
|
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
|
||||||
env:
|
env:
|
||||||
@ -205,6 +206,7 @@ spec:
|
|||||||
- --provider=cloudflare
|
- --provider=cloudflare
|
||||||
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
|
- --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-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
|
||||||
|
- --cloudflare-regional-services # (optional) enable the regional hostname feature that configure which region can decrypt HTTPS requests
|
||||||
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
|
- --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
|
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
|
||||||
env:
|
env:
|
||||||
@ -303,13 +305,19 @@ kubectl delete -f externaldns.yaml
|
|||||||
|
|
||||||
Using the `external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"` annotation on your ingress, you can specify if the proxy feature of Cloudflare should be enabled for that record. This setting will override the global `--cloudflare-proxied` setting.
|
Using the `external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"` annotation on your ingress, you can specify if the proxy feature of Cloudflare should be enabled for that record. This setting will override the global `--cloudflare-proxied` setting.
|
||||||
|
|
||||||
## Setting cloudflare-region-key to configure regional services
|
## Setting cloudlfare regional services
|
||||||
|
|
||||||
Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on your ingress, you can restrict which data centers can decrypt and serve HTTPS traffic.
|
With Cloudflare regional services you can restrict which data centers can decrypt and serve HTTPS traffic.
|
||||||
|
|
||||||
|
Configuration of Cloudflare Regional Services is enabled by the `--cloudflare-regional-services` flag.
|
||||||
|
A default region can be defined using the `--cloudflare-region-key` flag.
|
||||||
|
|
||||||
|
Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on your ingress, you can specify the region for that record.
|
||||||
|
|
||||||
|
An empty string will result in no regional hostname configured.
|
||||||
|
|
||||||
**Accepted values for region key include:**
|
**Accepted values for region key include:**
|
||||||
|
|
||||||
- `earth` (default): All data centers (global)
|
|
||||||
- `eu`: European Union data centers only
|
- `eu`: European Union data centers only
|
||||||
- `us`: United States data centers only
|
- `us`: United States data centers only
|
||||||
- `ap`: Asia-Pacific data centers only
|
- `ap`: Asia-Pacific data centers only
|
||||||
@ -321,14 +329,11 @@ Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on
|
|||||||
- `br`: Brazil data centers only
|
- `br`: Brazil data centers only
|
||||||
- `za`: South Africa data centers only
|
- `za`: South Africa data centers only
|
||||||
- `ae`: United Arab Emirates data centers only
|
- `ae`: United Arab Emirates data centers only
|
||||||
- `global`: Alias for `earth`
|
|
||||||
|
|
||||||
For the most up-to-date list and details, see the [Cloudflare Regional Services documentation](https://developers.cloudflare.com/data-localization/regional-services/get-started/).
|
For the most up-to-date list and details, see the [Cloudflare Regional Services documentation](https://developers.cloudflare.com/data-localization/regional-services/get-started/).
|
||||||
|
|
||||||
Currently, requires SuperAdmin or Admin role.
|
Currently, requires SuperAdmin or Admin role.
|
||||||
|
|
||||||
If not set the value will default to `global`.
|
|
||||||
|
|
||||||
## Setting cloudflare-custom-hostname
|
## Setting cloudflare-custom-hostname
|
||||||
|
|
||||||
Automatic configuration of Cloudflare custom hostnames (using A/CNAME DNS records as custom origin servers) is enabled by the `--cloudflare-custom-hostnames` flag and the `external-dns.alpha.kubernetes.io/cloudflare-custom-hostname: <custom hostname>` annotation.
|
Automatic configuration of Cloudflare custom hostnames (using A/CNAME DNS records as custom origin servers) is enabled by the `--cloudflare-custom-hostnames` flag and the `external-dns.alpha.kubernetes.io/cloudflare-custom-hostname: <custom hostname>` annotation.
|
||||||
|
@ -113,6 +113,7 @@ type Config struct {
|
|||||||
CloudflareDNSRecordsComment string
|
CloudflareDNSRecordsComment string
|
||||||
CloudflareCustomHostnamesMinTLSVersion string
|
CloudflareCustomHostnamesMinTLSVersion string
|
||||||
CloudflareCustomHostnamesCertificateAuthority string
|
CloudflareCustomHostnamesCertificateAuthority string
|
||||||
|
CloudflareRegionalServices bool
|
||||||
CloudflareRegionKey string
|
CloudflareRegionKey string
|
||||||
CloudflareRecordComment string
|
CloudflareRecordComment string
|
||||||
CoreDNSPrefix string
|
CoreDNSPrefix string
|
||||||
@ -256,6 +257,7 @@ var defaultConfig = &Config{
|
|||||||
CloudflareCustomHostnamesMinTLSVersion: "1.0",
|
CloudflareCustomHostnamesMinTLSVersion: "1.0",
|
||||||
CloudflareDNSRecordsPerPage: 100,
|
CloudflareDNSRecordsPerPage: 100,
|
||||||
CloudflareProxied: false,
|
CloudflareProxied: false,
|
||||||
|
CloudflareRegionalServices: false,
|
||||||
CloudflareRegionKey: "earth",
|
CloudflareRegionKey: "earth",
|
||||||
|
|
||||||
CombineFQDNAndAnnotation: false,
|
CombineFQDNAndAnnotation: false,
|
||||||
@ -533,7 +535,8 @@ func App(cfg *Config) *kingpin.Application {
|
|||||||
app.Flag("cloudflare-custom-hostnames-min-tls-version", "When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3)").Default("1.0").EnumVar(&cfg.CloudflareCustomHostnamesMinTLSVersion, "1.0", "1.1", "1.2", "1.3")
|
app.Flag("cloudflare-custom-hostnames-min-tls-version", "When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3)").Default("1.0").EnumVar(&cfg.CloudflareCustomHostnamesMinTLSVersion, "1.0", "1.1", "1.2", "1.3")
|
||||||
app.Flag("cloudflare-custom-hostnames-certificate-authority", "When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none)").Default("none").EnumVar(&cfg.CloudflareCustomHostnamesCertificateAuthority, "google", "ssl_com", "lets_encrypt", "none")
|
app.Flag("cloudflare-custom-hostnames-certificate-authority", "When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none)").Default("none").EnumVar(&cfg.CloudflareCustomHostnamesCertificateAuthority, "google", "ssl_com", "lets_encrypt", "none")
|
||||||
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-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-regional-services", "When using the Cloudflare provider, specify if Regional Services feature will be used (default: disabled)").Default(strconv.FormatBool(defaultConfig.CloudflareRegionalServices)).BoolVar(&cfg.CloudflareRegionalServices)
|
||||||
|
app.Flag("cloudflare-region-key", "When using the Cloudflare provider, specify the default region for Regional Services. Any value other than an empty string will enable the Regional Services feature (optional)").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("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("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
|
||||||
|
@ -186,6 +186,7 @@ var (
|
|||||||
CloudflareCustomHostnamesMinTLSVersion: "1.3",
|
CloudflareCustomHostnamesMinTLSVersion: "1.3",
|
||||||
CloudflareCustomHostnamesCertificateAuthority: "google",
|
CloudflareCustomHostnamesCertificateAuthority: "google",
|
||||||
CloudflareDNSRecordsPerPage: 5000,
|
CloudflareDNSRecordsPerPage: 5000,
|
||||||
|
CloudflareRegionalServices: true,
|
||||||
CloudflareRegionKey: "us",
|
CloudflareRegionKey: "us",
|
||||||
CoreDNSPrefix: "/coredns/",
|
CoreDNSPrefix: "/coredns/",
|
||||||
AkamaiServiceConsumerDomain: "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
AkamaiServiceConsumerDomain: "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
||||||
@ -296,6 +297,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
"--cloudflare-custom-hostnames-min-tls-version=1.3",
|
"--cloudflare-custom-hostnames-min-tls-version=1.3",
|
||||||
"--cloudflare-custom-hostnames-certificate-authority=google",
|
"--cloudflare-custom-hostnames-certificate-authority=google",
|
||||||
"--cloudflare-dns-records-per-page=5000",
|
"--cloudflare-dns-records-per-page=5000",
|
||||||
|
"--cloudflare-regional-services",
|
||||||
"--cloudflare-region-key=us",
|
"--cloudflare-region-key=us",
|
||||||
"--coredns-prefix=/coredns/",
|
"--coredns-prefix=/coredns/",
|
||||||
"--akamai-serviceconsumerdomain=oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
"--akamai-serviceconsumerdomain=oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
||||||
@ -424,6 +426,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_MIN_TLS_VERSION": "1.3",
|
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_MIN_TLS_VERSION": "1.3",
|
||||||
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_CERTIFICATE_AUTHORITY": "google",
|
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_CERTIFICATE_AUTHORITY": "google",
|
||||||
"EXTERNAL_DNS_CLOUDFLARE_DNS_RECORDS_PER_PAGE": "5000",
|
"EXTERNAL_DNS_CLOUDFLARE_DNS_RECORDS_PER_PAGE": "5000",
|
||||||
|
"EXTERNAL_DNS_CLOUDFLARE_REGIONAL_SERVICES": "1",
|
||||||
"EXTERNAL_DNS_CLOUDFLARE_REGION_KEY": "us",
|
"EXTERNAL_DNS_CLOUDFLARE_REGION_KEY": "us",
|
||||||
"EXTERNAL_DNS_COREDNS_PREFIX": "/coredns/",
|
"EXTERNAL_DNS_COREDNS_PREFIX": "/coredns/",
|
||||||
"EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN": "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
"EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN": "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
|
||||||
|
@ -119,6 +119,7 @@ type cloudFlareDNS interface {
|
|||||||
CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error)
|
CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error)
|
||||||
DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error
|
DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error
|
||||||
UpdateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDNSRecordParams) error
|
UpdateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDNSRecordParams) error
|
||||||
|
ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error)
|
||||||
CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error
|
CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error
|
||||||
UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error
|
UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error
|
||||||
DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error
|
DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error
|
||||||
@ -222,7 +223,7 @@ type CloudFlareProvider struct {
|
|||||||
DryRun bool
|
DryRun bool
|
||||||
CustomHostnamesConfig CustomHostnamesConfig
|
CustomHostnamesConfig CustomHostnamesConfig
|
||||||
DNSRecordsConfig DNSRecordsConfig
|
DNSRecordsConfig DNSRecordsConfig
|
||||||
RegionKey string
|
RegionalServicesConfig RegionalServicesConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// cloudFlareChange differentiates between ChangActions
|
// cloudFlareChange differentiates between ChangActions
|
||||||
@ -279,7 +280,15 @@ func convertCloudflareError(err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
|
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
|
||||||
func NewCloudFlareProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, regionKey string, customHostnamesConfig CustomHostnamesConfig, dnsRecordsConfig DNSRecordsConfig) (*CloudFlareProvider, error) {
|
func NewCloudFlareProvider(
|
||||||
|
domainFilter *endpoint.DomainFilter,
|
||||||
|
zoneIDFilter provider.ZoneIDFilter,
|
||||||
|
proxiedByDefault bool,
|
||||||
|
dryRun bool,
|
||||||
|
regionalServicesConfig RegionalServicesConfig,
|
||||||
|
customHostnamesConfig CustomHostnamesConfig,
|
||||||
|
dnsRecordsConfig DNSRecordsConfig,
|
||||||
|
) (*CloudFlareProvider, error) {
|
||||||
// initialize via chosen auth method and returns new API object
|
// initialize via chosen auth method and returns new API object
|
||||||
var (
|
var (
|
||||||
config *cloudflare.API
|
config *cloudflare.API
|
||||||
@ -302,6 +311,10 @@ func NewCloudFlareProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter pro
|
|||||||
return nil, fmt.Errorf("failed to initialize cloudflare provider: %w", err)
|
return nil, fmt.Errorf("failed to initialize cloudflare provider: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if regionalServicesConfig.RegionKey != "" {
|
||||||
|
regionalServicesConfig.Enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
return &CloudFlareProvider{
|
return &CloudFlareProvider{
|
||||||
Client: zoneService{config},
|
Client: zoneService{config},
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
@ -309,7 +322,7 @@ func NewCloudFlareProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter pro
|
|||||||
proxiedByDefault: proxiedByDefault,
|
proxiedByDefault: proxiedByDefault,
|
||||||
CustomHostnamesConfig: customHostnamesConfig,
|
CustomHostnamesConfig: customHostnamesConfig,
|
||||||
DryRun: dryRun,
|
DryRun: dryRun,
|
||||||
RegionKey: regionKey,
|
RegionalServicesConfig: regionalServicesConfig,
|
||||||
DNSRecordsConfig: dnsRecordsConfig,
|
DNSRecordsConfig: dnsRecordsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -379,7 +392,13 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
|||||||
// As CloudFlare does not support "sets" of targets, but instead returns
|
// As CloudFlare does not support "sets" of targets, but instead returns
|
||||||
// a single entry for each name/type/target, we have to group by name
|
// a single entry for each name/type/target, we have to group by name
|
||||||
// and record to allow the planner to calculate the correct plan. See #992.
|
// and record to allow the planner to calculate the correct plan. See #992.
|
||||||
endpoints = append(endpoints, groupByNameAndTypeWithCustomHostnames(records, chs)...)
|
zoneEndpoints := groupByNameAndTypeWithCustomHostnames(records, chs)
|
||||||
|
|
||||||
|
if err := p.addEnpointsProviderSpecificRegionKeyProperty(ctx, zone.ID, zoneEndpoints); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints = append(endpoints, zoneEndpoints...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
@ -595,16 +614,21 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if regionalHostnamesChanges, err := dataLocalizationRegionalHostnamesChanges(zoneChanges); err == nil {
|
if p.RegionalServicesConfig.Enabled {
|
||||||
if !p.submitDataLocalizationRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
|
desiredRegionalHostnames, err := desiredRegionalHostnames(zoneChanges)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build desired regional hostnames: %w", err)
|
||||||
|
}
|
||||||
|
if len(desiredRegionalHostnames) > 0 {
|
||||||
|
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, resourceContainer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not fetch regional hostnames from zone, %w", err)
|
||||||
|
}
|
||||||
|
regionalHostnamesChanges := regionalHostnamesChanges(desiredRegionalHostnames, regionalHostnames)
|
||||||
|
if !p.submitRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
|
||||||
failedChange = true
|
failedChange = true
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logFields := log.Fields{
|
|
||||||
"zone": zoneID,
|
|
||||||
}
|
}
|
||||||
log.WithFields(logFields).Errorf("failed to build data localization regional hostname changes: %v", err)
|
|
||||||
failedChange = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if failedChange {
|
if failedChange {
|
||||||
@ -640,6 +664,13 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]
|
|||||||
e.DeleteProviderSpecificProperty(annotations.CloudflareCustomHostnameKey)
|
e.DeleteProviderSpecificProperty(annotations.CloudflareCustomHostnameKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.RegionalServicesConfig.Enabled {
|
||||||
|
// Add default region key if not set
|
||||||
|
if _, ok := e.GetProviderSpecificProperty(annotations.CloudflareRegionKey); !ok {
|
||||||
|
e.SetProviderSpecificProperty(annotations.CloudflareRegionKey, p.RegionalServicesConfig.RegionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adjustedEndpoints = append(adjustedEndpoints, e)
|
adjustedEndpoints = append(adjustedEndpoints, e)
|
||||||
}
|
}
|
||||||
return adjustedEndpoints, nil
|
return adjustedEndpoints, nil
|
||||||
|
@ -18,30 +18,40 @@ package cloudflare
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflare-go"
|
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/source/annotations"
|
"sigs.k8s.io/external-dns/source/annotations"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RegionalServicesConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
RegionKey string
|
||||||
|
}
|
||||||
|
|
||||||
var recordTypeRegionalHostnameSupported = map[string]bool{
|
var recordTypeRegionalHostnameSupported = map[string]bool{
|
||||||
"A": true,
|
"A": true,
|
||||||
"AAAA": true,
|
"AAAA": true,
|
||||||
"CNAME": true,
|
"CNAME": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegionalHostnamesMap is a map of regional hostnames keyed by hostname.
|
||||||
|
type RegionalHostnamesMap map[string]cloudflare.RegionalHostname
|
||||||
|
|
||||||
type regionalHostnameChange struct {
|
type regionalHostnameChange struct {
|
||||||
action changeAction
|
action changeAction
|
||||||
cloudflare.RegionalHostname
|
cloudflare.RegionalHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (z zoneService) ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error) {
|
||||||
|
return z.service.ListDataLocalizationRegionalHostnames(ctx, rc, rp)
|
||||||
|
}
|
||||||
|
|
||||||
func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
|
func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
|
||||||
_, err := z.service.CreateDataLocalizationRegionalHostname(ctx, rc, rp)
|
_, err := z.service.CreateDataLocalizationRegionalHostname(ctx, rc, rp)
|
||||||
return err
|
return err
|
||||||
@ -72,89 +82,78 @@ func updateDataLocalizationRegionalHostnameParams(rhc regionalHostnameChange) cl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// submitDataLocalizationRegionalHostnameChanges applies a set of data localization regional hostname changes, returns false if it fails
|
// submitRegionalHostnameChanges applies a set of regional hostname changes, returns false if at least one fails
|
||||||
func (p *CloudFlareProvider) submitDataLocalizationRegionalHostnameChanges(ctx context.Context, rhChanges []regionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
|
func (p *CloudFlareProvider) submitRegionalHostnameChanges(ctx context.Context, rhChanges []regionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
|
||||||
failedChange := false
|
failedChange := false
|
||||||
|
|
||||||
for _, rhChange := range rhChanges {
|
for _, rhChange := range rhChanges {
|
||||||
logFields := log.Fields{
|
if !p.submitRegionalHostnameChange(ctx, rhChange, resourceContainer) {
|
||||||
"hostname": rhChange.Hostname,
|
|
||||||
"region_key": rhChange.RegionKey,
|
|
||||||
"action": rhChange.action,
|
|
||||||
"zone": resourceContainer.Identifier,
|
|
||||||
}
|
|
||||||
log.WithFields(logFields).Info("Changing regional hostname")
|
|
||||||
switch rhChange.action {
|
|
||||||
case cloudFlareCreate:
|
|
||||||
log.WithFields(logFields).Debug("Creating regional hostname")
|
|
||||||
if p.DryRun {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
regionalHostnameParam := createDataLocalizationRegionalHostnameParams(rhChange)
|
|
||||||
err := p.Client.CreateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam)
|
|
||||||
if err != nil {
|
|
||||||
var apiErr *cloudflare.Error
|
|
||||||
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict {
|
|
||||||
log.WithFields(logFields).Debug("Regional hostname already exists, updating instead")
|
|
||||||
params := updateDataLocalizationRegionalHostnameParams(rhChange)
|
|
||||||
err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, resourceContainer, params)
|
|
||||||
if err != nil {
|
|
||||||
failedChange = true
|
failedChange = true
|
||||||
log.WithFields(logFields).Errorf("failed to update regional hostname: %v", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
failedChange = true
|
|
||||||
log.WithFields(logFields).Errorf("failed to create regional hostname: %v", err)
|
|
||||||
}
|
|
||||||
case cloudFlareUpdate:
|
|
||||||
log.WithFields(logFields).Debug("Updating regional hostname")
|
|
||||||
if p.DryRun {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
regionalHostnameParam := updateDataLocalizationRegionalHostnameParams(rhChange)
|
|
||||||
err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam)
|
|
||||||
if err != nil {
|
|
||||||
var apiErr *cloudflare.Error
|
|
||||||
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
|
|
||||||
log.WithFields(logFields).Debug("Regional hostname not does not exists, creating instead")
|
|
||||||
params := createDataLocalizationRegionalHostnameParams(rhChange)
|
|
||||||
err := p.Client.CreateDataLocalizationRegionalHostname(ctx, resourceContainer, params)
|
|
||||||
if err != nil {
|
|
||||||
failedChange = true
|
|
||||||
log.WithFields(logFields).Errorf("failed to create regional hostname: %v", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
failedChange = true
|
|
||||||
log.WithFields(logFields).Errorf("failed to update regional hostname: %v", err)
|
|
||||||
}
|
|
||||||
case cloudFlareDelete:
|
|
||||||
log.WithFields(logFields).Debug("Deleting regional hostname")
|
|
||||||
if p.DryRun {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := p.Client.DeleteDataLocalizationRegionalHostname(ctx, resourceContainer, rhChange.Hostname)
|
|
||||||
if err != nil {
|
|
||||||
var apiErr *cloudflare.Error
|
|
||||||
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
|
|
||||||
log.WithFields(logFields).Debug("Regional hostname does not exists, nothing to do")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
failedChange = true
|
|
||||||
log.WithFields(logFields).Errorf("failed to delete regional hostname: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !failedChange
|
return !failedChange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// submitRegionalHostnameChange applies a single regional hostname change, returns false if it fails
|
||||||
|
func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Context, rhChange regionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
|
||||||
|
changeLog := log.WithFields(log.Fields{
|
||||||
|
"hostname": rhChange.Hostname,
|
||||||
|
"region_key": rhChange.RegionKey,
|
||||||
|
"action": rhChange.action,
|
||||||
|
"zone": resourceContainer.Identifier,
|
||||||
|
})
|
||||||
|
if p.DryRun {
|
||||||
|
changeLog.Debug("Dry run: skipping regional hostname change", rhChange.action)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch rhChange.action {
|
||||||
|
case cloudFlareCreate:
|
||||||
|
changeLog.Debug("Creating regional hostname")
|
||||||
|
regionalHostnameParam := createDataLocalizationRegionalHostnameParams(rhChange)
|
||||||
|
if err := p.Client.CreateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam); err != nil {
|
||||||
|
changeLog.Errorf("failed to create regional hostname: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case cloudFlareUpdate:
|
||||||
|
changeLog.Debug("Updating regional hostname")
|
||||||
|
regionalHostnameParam := updateDataLocalizationRegionalHostnameParams(rhChange)
|
||||||
|
if err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam); err != nil {
|
||||||
|
changeLog.Errorf("failed to update regional hostname: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case cloudFlareDelete:
|
||||||
|
changeLog.Debug("Deleting regional hostname")
|
||||||
|
if err := p.Client.DeleteDataLocalizationRegionalHostname(ctx, resourceContainer, rhChange.Hostname); err != nil {
|
||||||
|
changeLog.Errorf("failed to delete regional hostname: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CloudFlareProvider) listDataLocalisationRegionalHostnames(ctx context.Context, resourceContainer *cloudflare.ResourceContainer) (RegionalHostnamesMap, error) {
|
||||||
|
rhs, err := p.Client.ListDataLocalizationRegionalHostnames(ctx, resourceContainer, cloudflare.ListDataLocalizationRegionalHostnamesParams{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, convertCloudflareError(err)
|
||||||
|
}
|
||||||
|
rhsMap := make(RegionalHostnamesMap)
|
||||||
|
for _, r := range rhs {
|
||||||
|
rhsMap[r.Hostname] = r
|
||||||
|
}
|
||||||
|
return rhsMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// regionalHostname returns a RegionalHostname for the given endpoint.
|
||||||
|
//
|
||||||
|
// If the regional services feature is not enabled or the record type does not support regional hostnames,
|
||||||
|
// it returns an empty RegionalHostname.
|
||||||
|
// If the endpoint has a specific region key set, it uses that; otherwise, it defaults to the region key configured in the provider.
|
||||||
func (p *CloudFlareProvider) regionalHostname(ep *endpoint.Endpoint) cloudflare.RegionalHostname {
|
func (p *CloudFlareProvider) regionalHostname(ep *endpoint.Endpoint) cloudflare.RegionalHostname {
|
||||||
if p.RegionKey == "" || !recordTypeRegionalHostnameSupported[ep.RecordType] {
|
if !p.RegionalServicesConfig.Enabled || !recordTypeRegionalHostnameSupported[ep.RecordType] {
|
||||||
return cloudflare.RegionalHostname{}
|
return cloudflare.RegionalHostname{}
|
||||||
}
|
}
|
||||||
regionKey := p.RegionKey
|
regionKey := p.RegionalServicesConfig.RegionKey
|
||||||
if epRegionKey, exists := ep.GetProviderSpecificProperty(annotations.CloudflareRegionKey); exists {
|
if epRegionKey, exists := ep.GetProviderSpecificProperty(annotations.CloudflareRegionKey); exists {
|
||||||
regionKey = epRegionKey
|
regionKey = epRegionKey
|
||||||
}
|
}
|
||||||
@ -164,47 +163,109 @@ func (p *CloudFlareProvider) regionalHostname(ep *endpoint.Endpoint) cloudflare.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataLocalizationRegionalHostnamesChanges processes a slice of cloudFlare changes and consolidates them
|
// addEnpointsProviderSpecificRegionKeyProperty fetch the regional hostnames on cloudflare and
|
||||||
// into a list of data localization regional hostname changes.
|
// adds Cloudflare-specific region keys to the provided endpoints.
|
||||||
// returns nil if no changes are needed
|
//
|
||||||
func dataLocalizationRegionalHostnamesChanges(changes []*cloudFlareChange) ([]regionalHostnameChange, error) {
|
// Do nothing if the regional services feature is not enabled.
|
||||||
regionalHostnameChanges := make(map[string]regionalHostnameChange)
|
// Defaults to the region key configured in the provider config if not found in the regional hostnames.
|
||||||
|
func (p *CloudFlareProvider) addEnpointsProviderSpecificRegionKeyProperty(ctx context.Context, zoneID string, endpoints []*endpoint.Endpoint) error {
|
||||||
|
if !p.RegionalServicesConfig.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter endpoints to only those that support regional hostnames
|
||||||
|
// so we can skip regional hostname lookups if not needed.
|
||||||
|
var supportedEndpoints []*endpoint.Endpoint
|
||||||
|
for _, ep := range endpoints {
|
||||||
|
if recordTypeRegionalHostnameSupported[ep.RecordType] {
|
||||||
|
supportedEndpoints = append(supportedEndpoints, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(supportedEndpoints) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, cloudflare.ZoneIdentifier(zoneID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range supportedEndpoints {
|
||||||
|
if rh, found := regionalHostnames[ep.DNSName]; found {
|
||||||
|
ep.SetProviderSpecificProperty(annotations.CloudflareRegionKey, rh.RegionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// desiredRegionalHostnames builds a list of desired regional hostnames from changes.
|
||||||
|
//
|
||||||
|
// If there is a delete and a create or update action for the same hostname,
|
||||||
|
// The create or update takes precedence.
|
||||||
|
// Returns an error for conflicting region keys.
|
||||||
|
func desiredRegionalHostnames(changes []*cloudFlareChange) ([]cloudflare.RegionalHostname, error) {
|
||||||
|
rhs := make(map[string]cloudflare.RegionalHostname)
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
if change.RegionalHostname.Hostname == "" {
|
if change.RegionalHostname.Hostname == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if change.RegionalHostname.RegionKey == "" {
|
rh, found := rhs[change.RegionalHostname.Hostname]
|
||||||
return nil, fmt.Errorf("region key is empty for regional hostname %q", change.RegionalHostname.Hostname)
|
if !found {
|
||||||
}
|
if change.Action == cloudFlareDelete {
|
||||||
regionalHostname, ok := regionalHostnameChanges[change.RegionalHostname.Hostname]
|
rhs[change.RegionalHostname.Hostname] = cloudflare.RegionalHostname{
|
||||||
switch change.Action {
|
Hostname: change.RegionalHostname.Hostname,
|
||||||
case cloudFlareCreate, cloudFlareUpdate:
|
RegionKey: "", // Indicate that this regional hostname should not exists
|
||||||
if !ok {
|
|
||||||
regionalHostnameChanges[change.RegionalHostname.Hostname] = regionalHostnameChange{
|
|
||||||
action: change.Action,
|
|
||||||
RegionalHostname: change.RegionalHostname,
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if regionalHostname.RegionKey != change.RegionalHostname.RegionKey {
|
rhs[change.RegionalHostname.Hostname] = change.RegionalHostname
|
||||||
return nil, fmt.Errorf("conflicting region keys for regional hostname %q: %q and %q", change.RegionalHostname.Hostname, regionalHostname.RegionKey, change.RegionalHostname.RegionKey)
|
|
||||||
}
|
|
||||||
if (change.Action == cloudFlareUpdate && regionalHostname.action != cloudFlareUpdate) ||
|
|
||||||
regionalHostname.action == cloudFlareDelete {
|
|
||||||
regionalHostnameChanges[change.RegionalHostname.Hostname] = regionalHostnameChange{
|
|
||||||
action: cloudFlareUpdate,
|
|
||||||
RegionalHostname: change.RegionalHostname,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case cloudFlareDelete:
|
|
||||||
if !ok {
|
|
||||||
regionalHostnameChanges[change.RegionalHostname.Hostname] = regionalHostnameChange{
|
|
||||||
action: cloudFlareDelete,
|
|
||||||
RegionalHostname: change.RegionalHostname,
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if change.Action == cloudFlareDelete {
|
||||||
|
// A previous regional hostname exists so we can skip this delete action
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rh.RegionKey == "" {
|
||||||
|
// If the existing regional hostname has no region key, we can overwrite it
|
||||||
|
rhs[change.RegionalHostname.Hostname] = change.RegionalHostname
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rh.RegionKey != change.RegionalHostname.RegionKey {
|
||||||
|
return nil, fmt.Errorf("conflicting region keys for regional hostname %q: %q and %q", change.RegionalHostname.Hostname, rh.RegionKey, change.RegionalHostname.RegionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slices.Collect(maps.Values(regionalHostnameChanges)), nil
|
return slices.Collect(maps.Values(rhs)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// regionalHostnamesChanges build a list of changes needed to synchronize the current regional hostnames state with the desired state.
|
||||||
|
func regionalHostnamesChanges(desired []cloudflare.RegionalHostname, regionalHostnames RegionalHostnamesMap) []regionalHostnameChange {
|
||||||
|
changes := make([]regionalHostnameChange, 0)
|
||||||
|
for _, rh := range desired {
|
||||||
|
current, found := regionalHostnames[rh.Hostname]
|
||||||
|
if rh.RegionKey == "" {
|
||||||
|
// If the region key is empty, we don't want a regional hostname
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changes = append(changes, regionalHostnameChange{
|
||||||
|
action: cloudFlareDelete,
|
||||||
|
RegionalHostname: rh,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
changes = append(changes, regionalHostnameChange{
|
||||||
|
action: cloudFlareCreate,
|
||||||
|
RegionalHostname: rh,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rh.RegionKey != current.RegionKey {
|
||||||
|
changes = append(changes, regionalHostnameChange{
|
||||||
|
action: cloudFlareUpdate,
|
||||||
|
RegionalHostname: rh,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -30,7 +30,6 @@ import (
|
|||||||
"github.com/maxatome/go-testdeep/td"
|
"github.com/maxatome/go-testdeep/td"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
@ -55,6 +54,7 @@ type mockCloudFlareClient struct {
|
|||||||
listZonesContextError error
|
listZonesContextError error
|
||||||
dnsRecordsError error
|
dnsRecordsError error
|
||||||
customHostnames map[string][]cloudflare.CustomHostname
|
customHostnames map[string][]cloudflare.CustomHostname
|
||||||
|
regionalHostnames map[string][]cloudflare.RegionalHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
var ExampleDomain = []cloudflare.DNSRecord{
|
var ExampleDomain = []cloudflare.DNSRecord{
|
||||||
@ -96,6 +96,7 @@ func NewMockCloudFlareClient() *mockCloudFlareClient {
|
|||||||
"002": {},
|
"002": {},
|
||||||
},
|
},
|
||||||
customHostnames: map[string][]cloudflare.CustomHostname{},
|
customHostnames: map[string][]cloudflare.CustomHostname{},
|
||||||
|
regionalHostnames: map[string][]cloudflare.RegionalHostname{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,43 +228,6 @@ func (m *mockCloudFlareClient) UpdateDNSRecord(ctx context.Context, rc *cloudfla
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockCloudFlareClient) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
|
|
||||||
m.Actions = append(m.Actions, MockAction{
|
|
||||||
Name: "CreateDataLocalizationRegionalHostname",
|
|
||||||
ZoneId: rc.Identifier,
|
|
||||||
RecordId: "",
|
|
||||||
RegionalHostname: cloudflare.RegionalHostname{
|
|
||||||
Hostname: rp.Hostname,
|
|
||||||
RegionKey: rp.RegionKey,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockCloudFlareClient) UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error {
|
|
||||||
m.Actions = append(m.Actions, MockAction{
|
|
||||||
Name: "UpdateDataLocalizationRegionalHostname",
|
|
||||||
ZoneId: rc.Identifier,
|
|
||||||
RecordId: "",
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
Name: rp.Hostname,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockCloudFlareClient) DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error {
|
|
||||||
m.Actions = append(m.Actions, MockAction{
|
|
||||||
Name: "DeleteDataLocalizationRegionalHostname",
|
|
||||||
ZoneId: rc.Identifier,
|
|
||||||
RecordId: "",
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
Name: hostname,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockCloudFlareClient) DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error {
|
func (m *mockCloudFlareClient) DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error {
|
||||||
m.Actions = append(m.Actions, MockAction{
|
m.Actions = append(m.Actions, MockAction{
|
||||||
Name: "Delete",
|
Name: "Delete",
|
||||||
@ -756,110 +720,6 @@ func TestCloudflareSetProxied(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCloudflareRegionalHostname(t *testing.T) {
|
|
||||||
endpoints := []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
RecordType: "A",
|
|
||||||
DNSName: "bar.com",
|
|
||||||
Targets: endpoint.Targets{"127.0.0.1", "127.0.0.2"},
|
|
||||||
ProviderSpecific: endpoint.ProviderSpecific{
|
|
||||||
{
|
|
||||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-region-key",
|
|
||||||
Value: "eu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
AssertActions(t, &CloudFlareProvider{RegionKey: "us"}, endpoints, []MockAction{
|
|
||||||
{
|
|
||||||
Name: "Create",
|
|
||||||
ZoneId: "001",
|
|
||||||
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
ID: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
|
|
||||||
Type: "A",
|
|
||||||
Name: "bar.com",
|
|
||||||
Content: "127.0.0.1",
|
|
||||||
TTL: 1,
|
|
||||||
Proxied: proxyDisabled,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Create",
|
|
||||||
ZoneId: "001",
|
|
||||||
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
ID: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
|
|
||||||
Type: "A",
|
|
||||||
Name: "bar.com",
|
|
||||||
Content: "127.0.0.2",
|
|
||||||
TTL: 1,
|
|
||||||
Proxied: proxyDisabled,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CreateDataLocalizationRegionalHostname",
|
|
||||||
ZoneId: "001",
|
|
||||||
RegionalHostname: cloudflare.RegionalHostname{
|
|
||||||
Hostname: "bar.com",
|
|
||||||
RegionKey: "eu",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudflareRegionalHostnameDefaults(t *testing.T) {
|
|
||||||
endpoints := []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
RecordType: "A",
|
|
||||||
DNSName: "bar.com",
|
|
||||||
Targets: endpoint.Targets{"127.0.0.1", "127.0.0.2"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
AssertActions(t, &CloudFlareProvider{RegionKey: "us"}, endpoints, []MockAction{
|
|
||||||
{
|
|
||||||
Name: "Create",
|
|
||||||
ZoneId: "001",
|
|
||||||
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
ID: generateDNSRecordID("A", "bar.com", "127.0.0.1"),
|
|
||||||
Type: "A",
|
|
||||||
Name: "bar.com",
|
|
||||||
Content: "127.0.0.1",
|
|
||||||
TTL: 1,
|
|
||||||
Proxied: proxyDisabled,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Create",
|
|
||||||
ZoneId: "001",
|
|
||||||
RecordId: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
|
|
||||||
RecordData: cloudflare.DNSRecord{
|
|
||||||
ID: generateDNSRecordID("A", "bar.com", "127.0.0.2"),
|
|
||||||
Type: "A",
|
|
||||||
Name: "bar.com",
|
|
||||||
Content: "127.0.0.2",
|
|
||||||
TTL: 1,
|
|
||||||
Proxied: proxyDisabled,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CreateDataLocalizationRegionalHostname",
|
|
||||||
ZoneId: "001",
|
|
||||||
RegionalHostname: cloudflare.RegionalHostname{
|
|
||||||
Hostname: "bar.com",
|
|
||||||
RegionKey: "us",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudflareZones(t *testing.T) {
|
func TestCloudflareZones(t *testing.T) {
|
||||||
provider := &CloudFlareProvider{
|
provider := &CloudFlareProvider{
|
||||||
Client: NewMockCloudFlareClient(),
|
Client: NewMockCloudFlareClient(),
|
||||||
@ -1089,7 +949,7 @@ func TestCloudflareProvider(t *testing.T) {
|
|||||||
provider.NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
"",
|
RegionalServicesConfig{Enabled: false},
|
||||||
CustomHostnamesConfig{Enabled: false},
|
CustomHostnamesConfig{Enabled: false},
|
||||||
DNSRecordsConfig{PerPage: 5000, Comment: ""},
|
DNSRecordsConfig{PerPage: 5000, Comment: ""},
|
||||||
)
|
)
|
||||||
@ -1751,24 +1611,20 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCloudFlareProvider_Region(t *testing.T) {
|
func TestCloudFlareProvider_Region(t *testing.T) {
|
||||||
_ = os.Setenv("CF_API_TOKEN", "abc123def")
|
t.Setenv("CF_API_TOKEN", "abc123def")
|
||||||
_ = os.Setenv("CF_API_EMAIL", "test@test.com")
|
t.Setenv("CF_API_EMAIL", "test@test.com")
|
||||||
provider, err := NewCloudFlareProvider(
|
provider, err := NewCloudFlareProvider(
|
||||||
endpoint.NewDomainFilter([]string{"example.com"}),
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
||||||
provider.ZoneIDFilter{},
|
provider.ZoneIDFilter{},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"us",
|
RegionalServicesConfig{Enabled: false, RegionKey: "us"},
|
||||||
CustomHostnamesConfig{Enabled: false},
|
CustomHostnamesConfig{Enabled: false},
|
||||||
DNSRecordsConfig{PerPage: 50, Comment: ""},
|
DNSRecordsConfig{PerPage: 50, Comment: ""},
|
||||||
)
|
)
|
||||||
if err != nil {
|
assert.NoError(t, err, "should not fail to create provider")
|
||||||
t.Fatal(err)
|
assert.True(t, provider.RegionalServicesConfig.Enabled, "expect regional services to be enabled")
|
||||||
}
|
assert.Equal(t, "us", provider.RegionalServicesConfig.RegionKey, "expected region key to be 'us'")
|
||||||
|
|
||||||
if provider.RegionKey != "us" {
|
|
||||||
t.Errorf("expected region key to be 'us', but got '%s'", provider.RegionKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
||||||
@ -1780,7 +1636,7 @@ func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
|||||||
provider.ZoneIDFilter{},
|
provider.ZoneIDFilter{},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"us",
|
RegionalServicesConfig{Enabled: true, RegionKey: "us"},
|
||||||
CustomHostnamesConfig{Enabled: false},
|
CustomHostnamesConfig{Enabled: false},
|
||||||
DNSRecordsConfig{PerPage: 50},
|
DNSRecordsConfig{PerPage: 50},
|
||||||
)
|
)
|
||||||
@ -1823,7 +1679,7 @@ func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
|
|||||||
provider.ZoneIDFilter{},
|
provider.ZoneIDFilter{},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"us",
|
RegionalServicesConfig{Enabled: true, RegionKey: "us"},
|
||||||
CustomHostnamesConfig{Enabled: false},
|
CustomHostnamesConfig{Enabled: false},
|
||||||
DNSRecordsConfig{PerPage: 50, Comment: paidValidCommentBuilder.String()},
|
DNSRecordsConfig{PerPage: 50, Comment: paidValidCommentBuilder.String()},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user