1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package provider
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/netip"
"net/url"
)
type cloudflare struct {
log *slog.Logger
key string
}
var _ Provider = (*cloudflare)(nil)
func newCloudflare(log *slog.Logger, cfg Config) (*cloudflare, error) {
key, err := cfg.key()
if err != nil {
return nil, fmt.Errorf("failed instantiating cloudflare provider: %w", err)
}
return &cloudflare{key: key, log: log}, nil
}
const cfAPI = "https://api.cloudflare.com/client/v4"
func (c *cloudflare) headers() map[string]string {
return map[string]string{"Authorization": "Bearer " + c.key}
}
func (c *cloudflare) Ensure(zone, name string, rtype RecordType, addr netip.Addr) (string, bool, error) {
c.log.Info("updating record via cloudflare api")
var zones struct {
Result []struct {
ID string `json:"id"`
} `json:"result"`
}
raw, err := request("GET", cfAPI+"/zones?name="+url.QueryEscape(zone),
c.headers(), nil)
if err != nil {
return "", false, err
}
if err := json.Unmarshal(raw, &zones); err != nil {
return "", false, err
}
if len(zones.Result) == 0 {
return "", false, fmt.Errorf("zone %s not found in cloudflare", zone)
}
zoneID := zones.Result[0].ID
fqdn := name + "." + zone
var records struct {
Result []struct {
ID string `json:"id"`
Content string `json:"content"`
} `json:"result"`
}
raw, err = request("GET", fmt.Sprintf("%s/zones/%s/dns_records?type=%s&name=%s",
cfAPI, zoneID, string(rtype), url.QueryEscape(fqdn)), c.headers(), nil)
if err != nil {
return "", false, fmt.Errorf("failed to search for record in cloudflare: %w", err)
}
if err := json.Unmarshal(raw, &records); err != nil {
return "", false, fmt.Errorf("received malformed search results from cloudflare: %w", err)
}
body, _ := json.Marshal(map[string]any{
"type": string(rtype),
"name": fqdn,
"content": addr.String(),
"ttl": 300,
})
if len(records.Result) == 0 {
// if nothing found, create
c.log.Info("record not found in cloudflare, creating")
_, err = request("POST", fmt.Sprintf("%s/zones/%s/dns_records", cfAPI, zoneID),
c.headers(), bytes.NewReader(body))
return "", true, err
}
rec := records.Result[0]
if rec.Content == addr.String() {
return rec.Content, false, nil
}
// if record found, update
c.log.Info("record found in cloudflare, updating", slog.Group("cloudflare", slog.String("id", rec.ID), slog.String("content", rec.Content)))
_, err = request("PUT", fmt.Sprintf("%s/zones/%s/dns_records/%s", cfAPI, zoneID, rec.ID),
c.headers(), bytes.NewReader(body))
return rec.Content, true, err
}
|