forked from libdns/gandi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
219 lines (172 loc) · 5.71 KB
/
client.go
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package gandi
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/libdns/libdns"
)
func (p *Provider) setRecord(ctx context.Context, zone string, record libdns.Record, domain gandiDomain) error {
p.mutex.Lock()
defer p.mutex.Unlock()
// Append a dot to get the absolute record name.
recAbsoluteName := record.Name + "."
if !strings.HasSuffix(recAbsoluteName, zone) {
recAbsoluteName = recAbsoluteName + zone
}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, recAbsoluteName, record.Type), nil)
if err != nil {
return err
}
var oldGandiRecord gandiRecord
status, err := p.doRequest(req, &oldGandiRecord)
// ignore if no record is found, then we can create one safely
if status.Code != http.StatusNotFound && err != nil {
return err
}
// we check if the new value does not already exists and append it if not
var exists bool
for _, val := range oldGandiRecord.RRSetValues {
if val == record.Value {
exists = true
break
}
}
recValues := oldGandiRecord.RRSetValues
if !exists {
recValues = append(recValues, record.Value)
}
// we just create a new record, if an existing record was found, we just append the new value to the existing ones
newGandiRecord := gandiRecord{
RRSetTTL: int(record.TTL.Seconds()),
RRSetType: record.Type,
RRSetName: recAbsoluteName,
RRSetValues: recValues,
}
raw, err := json.Marshal(newGandiRecord)
if err != nil {
return err
}
// we update existing record or create a new record if it does not exist yet
req, err = http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, recAbsoluteName, record.Type), bytes.NewReader(raw))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
_, err = p.doRequest(req, nil)
return err
}
func (p *Provider) deleteRecord(ctx context.Context, zone string, record libdns.Record, domain gandiDomain) error {
p.mutex.Lock()
defer p.mutex.Unlock()
// Append a dot to get the absolute record name.
recAbsoluteName := record.Name + "."
if !strings.HasSuffix(recAbsoluteName, zone) {
recAbsoluteName = recAbsoluteName + zone
}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, recAbsoluteName, record.Type), nil)
if err != nil {
return err
}
// check if the record exists beforehand
var rec gandiRecord
_, err = p.doRequest(req, &rec)
if err != nil {
return err
}
if len(rec.RRSetValues) > 1 {
// if it contains multiple values, the best is to update the record instead of deleting all the values
for i, val := range rec.RRSetValues {
if val == record.Value {
rec.RRSetValues[len(rec.RRSetValues)-1], rec.RRSetValues[i] = rec.RRSetValues[i], rec.RRSetValues[len(rec.RRSetValues)-1]
rec.RRSetValues = rec.RRSetValues[:len(rec.RRSetValues)-1]
}
}
raw, err := json.Marshal(rec)
if err != nil {
return err
}
req, err = http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, recAbsoluteName, record.Type), bytes.NewReader(raw))
if err != nil {
return err
}
} else {
// if there is only one entry, we make sure that the value to delete is matching the one we found
// otherwise we may delete the wrong record
if strings.Trim(rec.RRSetValues[0], "\"") != record.Value {
return fmt.Errorf("LiveDNS returned a %v (%v)", http.StatusNotFound, "Can't find such a DNS value")
}
req, err = http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, recAbsoluteName, record.Type), nil)
}
// we check if NewRequestWithContext threw an error
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
_, err = p.doRequest(req, nil)
return err
}
func (p *Provider) getDomain(ctx context.Context, zone string) (gandiDomain, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
// we trim the dot at the end of the zone name to get the fqdn
// and then use it to fetch domain information through the api
fqdn := strings.TrimRight(zone, ".")
if p.domains == nil {
p.domains = make(map[string]gandiDomain)
}
if domain, ok := p.domains[fqdn]; ok {
return domain, nil
}
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.gandi.net/v5/livedns/domains/%s", fqdn), nil)
if err != nil {
return gandiDomain{}, err
}
var domain gandiDomain
if _, err = p.doRequest(req, &domain); err != nil {
return gandiDomain{}, err
}
p.domains[fqdn] = domain
return domain, nil
}
func (p *Provider) doRequest(req *http.Request, result interface{}) (gandiStatus, error) {
auth, err := p.getAuthHeader()
if err != nil {
return gandiStatus{}, err
}
req.Header.Set("Authorization", auth)
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return gandiStatus{}, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var response gandiStatus
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return gandiStatus{}, err
}
return response, fmt.Errorf("LiveDNS returned a %v (%v)", response.Code, response.Message)
}
// the api does not return the json object on 201 or 204, so we just stop here
if resp.StatusCode > 200 {
return gandiStatus{}, nil
}
// if we get a 200, we parse the json object
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return gandiStatus{}, err
}
return gandiStatus{}, nil
}
func (p *Provider) getAuthHeader() (string, error) {
switch {
case p.APIToken != "":
return fmt.Sprintf("Apikey %s", p.APIToken), nil
case p.BearerToken != "":
return fmt.Sprintf("Bearer %s", p.BearerToken), nil
default:
return "", fmt.Errorf("no auth token configured")
}
}