wrapper.go raw
1 package sakuracloud
2
3 import (
4 "context"
5 "fmt"
6 "sync"
7
8 "github.com/go-acme/lego/v4/challenge/dns01"
9 "github.com/sacloud/iaas-api-go"
10 "github.com/sacloud/iaas-api-go/search"
11 )
12
13 // This mutex is required for concurrent updates.
14 // see: https://github.com/go-acme/lego/pull/850
15 var mu sync.Mutex
16
17 func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl int) error {
18 mu.Lock()
19 defer mu.Unlock()
20
21 zone, err := d.getHostedZone(ctx, fqdn)
22 if err != nil {
23 return err
24 }
25
26 subDomain, err := dns01.ExtractSubDomain(fqdn, zone.Name)
27 if err != nil {
28 return err
29 }
30
31 records := append(zone.Records, &iaas.DNSRecord{
32 Name: subDomain,
33 Type: "TXT",
34 RData: value,
35 TTL: ttl,
36 })
37
38 _, err = d.client.UpdateSettings(ctx, zone.ID, &iaas.DNSUpdateSettingsRequest{
39 Records: records,
40 SettingsHash: zone.SettingsHash,
41 })
42 if err != nil {
43 return fmt.Errorf("API call failed: %w", err)
44 }
45
46 return nil
47 }
48
49 func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) error {
50 mu.Lock()
51 defer mu.Unlock()
52
53 zone, err := d.getHostedZone(ctx, fqdn)
54 if err != nil {
55 return err
56 }
57
58 subDomain, err := dns01.ExtractSubDomain(fqdn, zone.Name)
59 if err != nil {
60 return err
61 }
62
63 var updRecords iaas.DNSRecords
64
65 for _, r := range zone.Records {
66 if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { //nolint:staticcheck // Clearer without De Morgan's law.
67 updRecords = append(updRecords, r)
68 }
69 }
70
71 settings := &iaas.DNSUpdateSettingsRequest{
72 Records: updRecords,
73 SettingsHash: zone.SettingsHash,
74 }
75
76 _, err = d.client.UpdateSettings(ctx, zone.ID, settings)
77 if err != nil {
78 return fmt.Errorf("API call failed: %w", err)
79 }
80
81 return nil
82 }
83
84 func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*iaas.DNS, error) {
85 authZone, err := dns01.FindZoneByFqdn(domain)
86 if err != nil {
87 return nil, fmt.Errorf("could not find zone: %w", err)
88 }
89
90 zoneName := dns01.UnFqdn(authZone)
91
92 conditions := &iaas.FindCondition{
93 Filter: search.Filter{
94 search.Key("Name"): search.ExactMatch(zoneName),
95 },
96 }
97
98 res, err := d.client.Find(ctx, conditions)
99 if err != nil {
100 if iaas.IsNotFoundError(err) {
101 return nil, fmt.Errorf("zone %s not found on SakuraCloud DNS: %w", zoneName, err)
102 }
103
104 return nil, fmt.Errorf("API call failed: %w", err)
105 }
106
107 for _, zone := range res.DNS {
108 if zone.Name == zoneName {
109 return zone, nil
110 }
111 }
112
113 return nil, fmt.Errorf("zone %s not found", zoneName)
114 }
115