wrapper.go raw
1 package vinyldns
2
3 import (
4 "context"
5 "fmt"
6
7 "github.com/cenkalti/backoff/v5"
8 "github.com/go-acme/lego/v4/challenge/dns01"
9 "github.com/go-acme/lego/v4/platform/wait"
10 "github.com/vinyldns/go-vinyldns/vinyldns"
11 )
12
13 func (d *DNSProvider) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) {
14 zoneName, hostName, err := splitDomain(fqdn)
15 if err != nil {
16 return nil, err
17 }
18
19 zone, err := d.client.ZoneByName(zoneName)
20 if err != nil {
21 return nil, err
22 }
23
24 allRecordSets, err := d.client.RecordSetsListAll(zone.ID, vinyldns.ListFilter{NameFilter: hostName})
25 if err != nil {
26 return nil, err
27 }
28
29 var recordSets []vinyldns.RecordSet
30
31 for _, i := range allRecordSets {
32 if i.Type == "TXT" {
33 recordSets = append(recordSets, i)
34 }
35 }
36
37 switch {
38 case len(recordSets) > 1:
39 return nil, fmt.Errorf("ambiguous recordset definition of %s", fqdn)
40 case len(recordSets) == 1:
41 return &recordSets[0], nil
42 default:
43 return nil, nil
44 }
45 }
46
47 func (d *DNSProvider) createRecordSet(ctx context.Context, fqdn string, records []vinyldns.Record) error {
48 zoneName, hostName, err := splitDomain(fqdn)
49 if err != nil {
50 return err
51 }
52
53 zone, err := d.client.ZoneByName(zoneName)
54 if err != nil {
55 return err
56 }
57
58 recordSet := vinyldns.RecordSet{
59 Name: hostName,
60 ZoneID: zone.ID,
61 Type: "TXT",
62 TTL: d.config.TTL,
63 Records: records,
64 }
65
66 resp, err := d.client.RecordSetCreate(&recordSet)
67 if err != nil {
68 return err
69 }
70
71 return d.waitForChanges(ctx, "CreateRS", resp)
72 }
73
74 func (d *DNSProvider) updateRecordSet(ctx context.Context, recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error {
75 operation := "delete"
76 if len(recordSet.Records) < len(newRecords) {
77 operation = "add"
78 }
79
80 recordSet.Records = newRecords
81 recordSet.TTL = d.config.TTL
82
83 resp, err := d.client.RecordSetUpdate(recordSet)
84 if err != nil {
85 return err
86 }
87
88 return d.waitForChanges(ctx, "UpdateRS - "+operation, resp)
89 }
90
91 func (d *DNSProvider) deleteRecordSet(ctx context.Context, existingRecord *vinyldns.RecordSet) error {
92 resp, err := d.client.RecordSetDelete(existingRecord.ZoneID, existingRecord.ID)
93 if err != nil {
94 return err
95 }
96
97 return d.waitForChanges(ctx, "DeleteRS", resp)
98 }
99
100 func (d *DNSProvider) waitForChanges(ctx context.Context, operation string, resp *vinyldns.RecordSetUpdateResponse) error {
101 return wait.Retry(ctx,
102 func() error {
103 change, err := d.client.RecordSetChange(resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID)
104 if err != nil {
105 return fmt.Errorf("failed to query change status: %w", err)
106 }
107
108 if change.Status != "Complete" {
109 return fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s",
110 operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID)
111 }
112
113 return nil
114 },
115 backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)),
116 backoff.WithMaxElapsedTime(d.config.PropagationTimeout),
117 )
118 }
119
120 // splitDomain splits the hostname from the authoritative zone, and returns both parts.
121 func splitDomain(fqdn string) (string, string, error) {
122 zone, err := dns01.FindZoneByFqdn(fqdn)
123 if err != nil {
124 return "", "", fmt.Errorf("could not find zone: %w", err)
125 }
126
127 subDomain, err := dns01.ExtractSubDomain(fqdn, zone)
128 if err != nil {
129 return "", "", err
130 }
131
132 return zone, subDomain, nil
133 }
134