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