Skip to content

Commit

Permalink
CLOUDFLARE: adopt ZoneCache (#3373)
Browse files Browse the repository at this point in the history
  • Loading branch information
das7pad authored Jan 15, 2025
1 parent 2ef2362 commit 0d5b3c2
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 69 deletions.
65 changes: 19 additions & 46 deletions providers/cloudflare/cloudflareProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ import (
"os"
"strconv"
"strings"
"sync"

"github.com/cloudflare/cloudflare-go"
"github.com/fatih/color"
"golang.org/x/net/idna"

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/transform"
"github.com/StackExchange/dnscontrol/v4/pkg/zoneCache"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/StackExchange/dnscontrol/v4/providers/cloudflare/rtypes/cfsingleredirect"
"github.com/cloudflare/cloudflare-go"
"github.com/fatih/color"
"golang.org/x/net/idna"
)

/*
Expand Down Expand Up @@ -93,39 +94,21 @@ type cloudflareProvider struct {
tcLogFh *os.File // Transcode Log file handle
tcZone string // Transcode Current zone

sync.Mutex // Protects all access to the following fields:
domainIndex map[string]string // Cache of zone name to zone ID.
nameservers map[string][]string // Cache of zone name to list of nameservers.
zoneCache zoneCache.ZoneCache[cloudflare.Zone]
}

// GetNameservers returns the nameservers for a domain.
func (c *cloudflareProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
c.Lock()
defer c.Unlock()
if err := c.cacheDomainList(); err != nil {
z, err := c.zoneCache.GetZone(domain)
if err != nil {
return nil, err
}

ns, ok := c.nameservers[domain]
if !ok {
return nil, fmt.Errorf("nameservers for %s not found in cloudflare cache(%q)", domain, c.accountID)
}
return models.ToNameservers(ns)
return models.ToNameservers(z.NameServers)
}

// ListZones returns a list of the DNS zones.
func (c *cloudflareProvider) ListZones() ([]string, error) {
c.Lock()
defer c.Unlock()
if err := c.cacheDomainList(); err != nil {
return nil, err
}

zones := make([]string, 0, len(c.domainIndex))
for d := range c.domainIndex {
zones = append(zones, d)
}
return zones, nil
return c.zoneCache.GetZoneNames()
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
Expand Down Expand Up @@ -186,17 +169,11 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin
}

func (c *cloudflareProvider) getDomainID(name string) (string, error) {
c.Lock()
defer c.Unlock()
if err := c.cacheDomainList(); err != nil {
z, err := c.zoneCache.GetZone(name)
if err != nil {
return "", err
}

id, ok := c.domainIndex[name]
if !ok {
return "", fmt.Errorf("'%s' not a zone in cloudflare account", name)
}
return id, nil
return z.ID, nil
}

// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
Expand Down Expand Up @@ -652,6 +629,7 @@ func (c *cloudflareProvider) LogTranscode(zone string, redirect *models.Cloudfla

func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
api := &cloudflareProvider{}
api.zoneCache = zoneCache.New(api.fetchAllZones)
// check api keys from creds json file
if m["apitoken"] == "" && (m["apikey"] == "" || m["apiuser"] == "") {
return nil, errors.New("if cloudflare apitoken is not set, apikey and apiuser must be provided")
Expand Down Expand Up @@ -907,20 +885,15 @@ func getProxyMetadata(r *models.RecordConfig) map[string]string {

// EnsureZoneExists creates a zone if it does not exist
func (c *cloudflareProvider) EnsureZoneExists(domain string) error {
c.Lock()
defer c.Unlock()
if err := c.cacheDomainList(); err != nil {
if ok, err := c.zoneCache.HasZone(domain); err != nil || ok {
return err
}

if _, ok := c.domainIndex[domain]; ok {
return nil
}
var id string
id, err := c.createZone(domain)
if err != nil {
return err
}
printer.Printf("Added zone for %s to Cloudflare account: %s\n", domain, id)
clear(c.domainIndex) // clear the cache so that the next caller has to refresh it, thus loading the new ID.
return err
return nil
}

// PrepareCloudflareTestWorkers creates Cloudflare Workers required for CF_WORKER_ROUTE integration tests.
Expand Down
42 changes: 19 additions & 23 deletions providers/cloudflare/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,33 @@ import (
"fmt"
"strings"

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/providers/cloudflare/rtypes/cfsingleredirect"
"github.com/cloudflare/cloudflare-go"
"golang.org/x/net/idna"
)

// get list of domains for account. Cache so the ids can be looked up from domain name
// The caller must do all locking.
func (c *cloudflareProvider) cacheDomainList() error {
if c.domainIndex != nil {
return nil
}
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/providers/cloudflare/rtypes/cfsingleredirect"
)

// fmt.Printf("DEBUG: CLOUDFLARE POPULATING CACHE\n")
func (c *cloudflareProvider) fetchAllZones() (map[string]cloudflare.Zone, error) {
zones, err := c.cfClient.ListZones(context.Background())
if err != nil {
return fmt.Errorf("failed fetching domain list from cloudflare(%q): %w", c.cfClient.APIEmail, err)
return nil, fmt.Errorf("failed fetching domain list from cloudflare(%q): %w", c.cfClient.APIEmail, err)
}

c.domainIndex = map[string]string{}
c.nameservers = map[string][]string{}

m := make(map[string]cloudflare.Zone, len(zones))
for _, zone := range zones {
if encoded, err := idna.ToASCII(zone.Name); err == nil && encoded != zone.Name {
if _, ok := c.domainIndex[encoded]; ok {
if _, ok := m[encoded]; ok {
fmt.Printf("WARNING: Zone %q appears twice in this cloudflare account\n", encoded)
}
c.domainIndex[encoded] = zone.ID
c.nameservers[encoded] = zone.NameServers
m[encoded] = zone
}
if _, ok := c.domainIndex[zone.Name]; ok {
if _, ok := m[zone.Name]; ok {
fmt.Printf("WARNING: Zone %q appears twice in this cloudflare account\n", zone.Name)
}
c.domainIndex[zone.Name] = zone.ID
c.nameservers[zone.Name] = zone.NameServers
m[zone.Name] = zone
}

return nil
return m, nil
}

// get all records for a domain
Expand All @@ -69,7 +58,14 @@ func (c *cloudflareProvider) deleteDNSRecord(rec cloudflare.DNSRecord, domainID

func (c *cloudflareProvider) createZone(domainName string) (string, error) {
zone, err := c.cfClient.CreateZone(context.Background(), domainName, false, cloudflare.Account{ID: c.accountID}, "full")
return zone.ID, err
if err != nil {
return "", err
}
if encoded, err := idna.ToASCII(zone.Name); err == nil && encoded != zone.Name {
c.zoneCache.SetZone(encoded, zone)
}
c.zoneCache.SetZone(domainName, zone)
return zone.ID, nil
}

func cfDnskeyData(rec *models.RecordConfig) *cfRecData {
Expand Down

0 comments on commit 0d5b3c2

Please sign in to comment.