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
208 changes: 18 additions & 190 deletions provider/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ import (
"sigs.k8s.io/external-dns/source/annotations"
)

type changeAction int

const (
// cloudFlareCreate is a ChangeAction enum value
cloudFlareCreate = "CREATE"
cloudFlareCreate changeAction = iota
// cloudFlareDelete is a ChangeAction enum value
cloudFlareDelete = "DELETE"
cloudFlareDelete
// cloudFlareUpdate is a ChangeAction enum value
cloudFlareUpdate = "UPDATE"
cloudFlareUpdate
// defaultTTL 1 = automatic
defaultTTL = 1

Expand All @@ -53,6 +55,16 @@ const (
paidZoneMaxCommentLength = 500
)

var changeActionNames = map[changeAction]string{
cloudFlareCreate: "CREATE",
cloudFlareDelete: "DELETE",
cloudFlareUpdate: "UPDATE",
}

func (action changeAction) String() string {
return changeActionNames[action]
}

// We have to use pointers to bools now, as the upstream cloudflare-go library requires them
// see: https://github.com/cloudflare/cloudflare-go/pull/595

Expand All @@ -78,11 +90,6 @@ type CustomHostnameIndex struct {

type CustomHostnamesMap map[CustomHostnameIndex]cloudflare.CustomHostname

type DataLocalizationRegionalHostnameChange struct {
Action string
cloudflare.RegionalHostname
}

var recordTypeProxyNotSupported = map[string]bool{
"LOC": true,
"MX": true,
Expand All @@ -103,12 +110,6 @@ var recordTypeCustomHostnameSupported = map[string]bool{
"CNAME": true,
}

var recordTypeRegionalHostnameSupported = map[string]bool{
"A": true,
"AAAA": true,
"CNAME": true,
}

// cloudFlareDNS is the subset of the CloudFlare API that we actually use. Add methods as required. Signatures must match exactly.
type cloudFlareDNS interface {
UserDetails(ctx context.Context) (cloudflare.User, error)
Expand Down Expand Up @@ -157,20 +158,6 @@ func (z zoneService) UpdateDNSRecord(ctx context.Context, rc *cloudflare.Resourc
return err
}

func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
_, err := z.service.CreateDataLocalizationRegionalHostname(ctx, rc, rp)
return err
}

func (z zoneService) UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error {
_, err := z.service.UpdateDataLocalizationRegionalHostname(ctx, rc, rp)
return err
}

func (z zoneService) DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error {
return z.service.DeleteDataLocalizationRegionalHostname(ctx, rc, hostname)
}

func (z zoneService) DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error {
return z.service.DeleteDNSRecord(ctx, rc, recordID)
}
Expand Down Expand Up @@ -250,7 +237,7 @@ type CloudFlareProvider struct {

// cloudFlareChange differentiates between ChangActions
type cloudFlareChange struct {
Action string
Action changeAction
ResourceRecord cloudflare.DNSRecord
RegionalHostname cloudflare.RegionalHostname
CustomHostnames map[string]cloudflare.CustomHostname
Expand All @@ -273,22 +260,6 @@ func updateDNSRecordParam(cfc cloudFlareChange) cloudflare.UpdateDNSRecordParams
}
}

// createDataLocalizationRegionalHostnameParams is a function that returns the appropriate RegionalHostname Param based on the cloudFlareChange passed in
func createDataLocalizationRegionalHostnameParams(rhc DataLocalizationRegionalHostnameChange) cloudflare.CreateDataLocalizationRegionalHostnameParams {
return cloudflare.CreateDataLocalizationRegionalHostnameParams{
Hostname: rhc.Hostname,
RegionKey: rhc.RegionKey,
}
}

// updateDataLocalizationRegionalHostnameParams is a function that returns the appropriate RegionalHostname Param based on the cloudFlareChange passed in
func updateDataLocalizationRegionalHostnameParams(rhc DataLocalizationRegionalHostnameChange) cloudflare.UpdateDataLocalizationRegionalHostnameParams {
return cloudflare.UpdateDataLocalizationRegionalHostnameParams{
Hostname: rhc.Hostname,
RegionKey: rhc.RegionKey,
}
}

// getCreateDNSRecordParam is a function that returns the appropriate Record Param based on the cloudFlareChange passed in
func getCreateDNSRecordParam(cfc cloudFlareChange) cloudflare.CreateDNSRecordParams {
return cloudflare.CreateDNSRecordParams{
Expand Down Expand Up @@ -538,129 +509,6 @@ func (p *CloudFlareProvider) submitCustomHostnameChanges(ctx context.Context, zo
return !failedChange
}

// submitDataLocalizationRegionalHostnameChanges applies a set of data localization regional hostname changes, returns false if it fails
func (p *CloudFlareProvider) submitDataLocalizationRegionalHostnameChanges(ctx context.Context, changes []DataLocalizationRegionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
failedChange := false

for _, change := range changes {
logFields := log.Fields{
"hostname": change.Hostname,
"region_key": change.RegionKey,
"action": change.Action,
"zone": resourceContainer.Identifier,
}
log.WithFields(logFields).Info("Changing regional hostname")
switch change.Action {
case cloudFlareCreate:
log.WithFields(logFields).Debug("Creating regional hostname")
if p.DryRun {
continue
}
regionalHostnameParam := createDataLocalizationRegionalHostnameParams(change)
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(change)
err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, resourceContainer, params)
if err != nil {
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(change)
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(change)
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, change.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
}

// dataLocalizationRegionalHostnamesChanges processes a slice of cloudFlare changes and consolidates them
// into a list of data localization regional hostname changes.
// returns nil if no changes are needed
func dataLocalizationRegionalHostnamesChanges(changes []*cloudFlareChange) ([]DataLocalizationRegionalHostnameChange, error) {
regionalHostnameChanges := make(map[string]DataLocalizationRegionalHostnameChange)
for _, change := range changes {
if change.RegionalHostname.Hostname == "" {
continue
}
if change.RegionalHostname.RegionKey == "" {
return nil, fmt.Errorf("region key is empty for regional hostname %q", change.RegionalHostname.Hostname)
}
regionalHostname, ok := regionalHostnameChanges[change.RegionalHostname.Hostname]
switch change.Action {
case cloudFlareCreate, cloudFlareUpdate:
if !ok {
regionalHostnameChanges[change.RegionalHostname.Hostname] = DataLocalizationRegionalHostnameChange{
Action: change.Action,
RegionalHostname: change.RegionalHostname,
}
continue
}
if regionalHostname.RegionKey != change.RegionalHostname.RegionKey {
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] = DataLocalizationRegionalHostnameChange{
Action: cloudFlareUpdate,
RegionalHostname: change.RegionalHostname,
}
}
case cloudFlareDelete:
if !ok {
regionalHostnameChanges[change.RegionalHostname.Hostname] = DataLocalizationRegionalHostnameChange{
Action: cloudFlareDelete,
RegionalHostname: change.RegionalHostname,
}
continue
}
}
}
return slices.Collect(maps.Values(regionalHostnameChanges)), nil
}

// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloudFlareChange) error {
// return early if there is nothing to change
Expand Down Expand Up @@ -844,7 +692,7 @@ func (p *CloudFlareProvider) newCustomHostname(customHostname string, origin str
}
}

func (p *CloudFlareProvider) newCloudFlareChange(action string, ep *endpoint.Endpoint, target string, current *endpoint.Endpoint) *cloudFlareChange {
func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ep *endpoint.Endpoint, target string, current *endpoint.Endpoint) *cloudFlareChange {
ttl := defaultTTL
proxied := shouldBeProxied(ep, p.proxiedByDefault)

Expand All @@ -862,13 +710,6 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, ep *endpoint.End
newCustomHostnames[v] = p.newCustomHostname(v, ep.DNSName)
}
}
regionalHostname := cloudflare.RegionalHostname{}
if regionKey := getRegionKey(ep, p.RegionKey); regionKey != "" {
regionalHostname = cloudflare.RegionalHostname{
Hostname: ep.DNSName,
RegionKey: regionKey,
}
}

// Load comment from program flag
comment := p.DNSRecordsConfig.Comment
Expand All @@ -893,7 +734,7 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, ep *endpoint.End
Content: target,
Comment: comment,
},
RegionalHostname: regionalHostname,
RegionalHostname: p.regionalHostname(ep),
CustomHostnamesPrev: prevCustomHostnames,
CustomHostnames: newCustomHostnames,
}
Expand Down Expand Up @@ -1001,19 +842,6 @@ func shouldBeProxied(ep *endpoint.Endpoint, proxiedByDefault bool) bool {
return proxied
}

func getRegionKey(endpoint *endpoint.Endpoint, defaultRegionKey string) string {
if !recordTypeRegionalHostnameSupported[endpoint.RecordType] {
return ""
}

for _, v := range endpoint.ProviderSpecific {
if v.Name == annotations.CloudflareRegionKey {
return v.Value
}
}
return defaultRegionKey
}

func getEndpointCustomHostnames(ep *endpoint.Endpoint) []string {
for _, v := range ep.ProviderSpecific {
if v.Name == annotations.CloudflareCustomHostnameKey {
Expand Down
Loading
Loading