wrapper.go raw

   1  package cloudflare
   2  
   3  import (
   4  	"context"
   5  	"errors"
   6  	"sync"
   7  
   8  	"github.com/go-acme/lego/v4/challenge/dns01"
   9  	"github.com/go-acme/lego/v4/providers/dns/cloudflare/internal"
  10  )
  11  
  12  type metaClient struct {
  13  	clientEdit *internal.Client // needs Zone/DNS/Edit permissions
  14  	clientRead *internal.Client // needs Zone/Zone/Read permissions
  15  
  16  	zones   map[string]string // caches calls to ZoneIDByName, see lookupZoneID()
  17  	zonesMu *sync.RWMutex
  18  }
  19  
  20  func newClient(config *Config) (*metaClient, error) {
  21  	// with AuthKey/AuthEmail we can access all available APIs
  22  	if config.AuthToken == "" {
  23  		client, err := internal.NewClient(
  24  			internal.WithBaseURL(config.BaseURL),
  25  			internal.WithHTTPClient(config.HTTPClient),
  26  			internal.WithAuthKey(config.AuthEmail, config.AuthKey))
  27  		if err != nil {
  28  			return nil, err
  29  		}
  30  
  31  		return &metaClient{
  32  			clientEdit: client,
  33  			clientRead: client,
  34  			zones:      make(map[string]string),
  35  			zonesMu:    &sync.RWMutex{},
  36  		}, nil
  37  	}
  38  
  39  	dns, err := internal.NewClient(
  40  		internal.WithBaseURL(config.BaseURL),
  41  		internal.WithHTTPClient(config.HTTPClient),
  42  		internal.WithAuthToken(config.AuthToken))
  43  	if err != nil {
  44  		return nil, err
  45  	}
  46  
  47  	if config.ZoneToken == "" || config.ZoneToken == config.AuthToken {
  48  		return &metaClient{
  49  			clientEdit: dns,
  50  			clientRead: dns,
  51  			zones:      make(map[string]string),
  52  			zonesMu:    &sync.RWMutex{},
  53  		}, nil
  54  	}
  55  
  56  	zone, err := internal.NewClient(
  57  		internal.WithBaseURL(config.BaseURL),
  58  		internal.WithHTTPClient(config.HTTPClient),
  59  		internal.WithAuthToken(config.ZoneToken))
  60  	if err != nil {
  61  		return nil, err
  62  	}
  63  
  64  	return &metaClient{
  65  		clientEdit: dns,
  66  		clientRead: zone,
  67  		zones:      make(map[string]string),
  68  		zonesMu:    &sync.RWMutex{},
  69  	}, nil
  70  }
  71  
  72  func (m *metaClient) CreateDNSRecord(ctx context.Context, zoneID string, rr internal.Record) (*internal.Record, error) {
  73  	return m.clientEdit.CreateDNSRecord(ctx, zoneID, rr)
  74  }
  75  
  76  func (m *metaClient) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error {
  77  	return m.clientEdit.DeleteDNSRecord(ctx, zoneID, recordID)
  78  }
  79  
  80  func (m *metaClient) ZoneIDByName(ctx context.Context, fdqn string) (string, error) {
  81  	m.zonesMu.RLock()
  82  	id := m.zones[fdqn]
  83  	m.zonesMu.RUnlock()
  84  
  85  	if id != "" {
  86  		return id, nil
  87  	}
  88  
  89  	zones, err := m.clientRead.ZonesByName(ctx, dns01.UnFqdn(fdqn))
  90  	if err != nil {
  91  		return "", err
  92  	}
  93  
  94  	id, err = extractZoneID(zones)
  95  	if err != nil {
  96  		return "", err
  97  	}
  98  
  99  	m.zonesMu.Lock()
 100  	m.zones[fdqn] = id
 101  	m.zonesMu.Unlock()
 102  
 103  	return id, nil
 104  }
 105  
 106  func extractZoneID(res []internal.Zone) (string, error) {
 107  	switch len(res) {
 108  	case 0:
 109  		return "", errors.New("zone could not be found")
 110  	case 1:
 111  		return res[0].ID, nil
 112  	default:
 113  		return "", errors.New("ambiguous zone name; an account ID might help")
 114  	}
 115  }
 116