-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.go
221 lines (195 loc) · 5.95 KB
/
helpers.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
220
221
package selectelv2
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/libdns/libdns"
)
// Generic function to deserialize a JSON string into a variable of type T
func deserialization[T any](data []byte) (T, error) {
var result T
if err := json.Unmarshal(data, &result); err != nil {
return result, err
}
return result, nil
}
// Generate url from path
func urlGenerator(path string, args ...interface{}) string {
return fmt.Sprintf(cApiBaseUrl+path, args...)
}
// API request function
// ctx - context
// method - http method (GET, POST, DELETE, PATCH, PUT)
// path - path to connect to apiBaseUrl
// body - data transferred in the body
// hideToken - flag for hiding the token in the header
// args - substitution arguments in path
func (p *Provider) makeApiRequest(ctx context.Context, method string, path string , body io.Reader, args ...interface{}) ([]byte, error) {
// Make request
request, err := http.NewRequestWithContext(ctx, method, urlGenerator(path, args...) , body)
if err != nil {
return nil, err
}
// add headers
request.Header.Add("X-Auth-Token", p.KeystoneToken)
request.Header.Add("Content-Type", "application/json")
// request api
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close() // Guaranteed to close the body after the function is completed
// get the body and return it
data, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
// check status
if response.StatusCode < 200 || response.StatusCode >= 300 {
return nil, fmt.Errorf("%s (%d): %s", http.StatusText(response.StatusCode), response.StatusCode, string(data))
}
return data, nil
}
// Get zoneId by zone name
func (p *Provider) getZoneID(ctx context.Context, zone string) (string, error) {
// try get zoneId from cache
zoneId := p.ZonesCache[zone]
if zoneId == "" {
// if not in cache, get from api
zonesB, err := p.makeApiRequest(ctx, httpMethods.get, fmt.Sprintf("/zones?filter=%s", url.QueryEscape(zone)), nil)
if err != nil {
return "", err
}
zones, err := deserialization[Zones](zonesB)
if err != nil {
return "", err
}
if len(zones.Zones) == 0 {
return "", fmt.Errorf("no zoneId for zone %s", zone)
}
zoneId = zones.Zones[0].ID_
}
return zoneId, nil
}
// Convert Record to libdns.Record
func recordToLibdns(zone string, record Record) libdns.Record {
// for TTL
ttlDuration := time.Duration(record.TTL) * time.Second
// for Value
var valueString string
for _, recVal := range record.Value {
valueString += recVal.Value + "\n"
}
// remove last \n
if len(valueString) > 0 {
valueString = valueString[:len(valueString)-1]
}
// for TXT raplace all \"
if record.Type == "TXT" {
valueString = strings.ReplaceAll(valueString, "\"", "")
}
return libdns.Record{
ID: record.ID,
Type: record.Type,
Name: nameNormalizer(record.Name, zone),
Value: valueString,
TTL: ttlDuration,
}
}
// map []Record to []libdns.Record
func mapRecordsToLibds(zone string, records []Record) []libdns.Record {
libdnsRecords := make([]libdns.Record, len(records))
for i, record := range records {
libdnsRecords[i] = recordToLibdns(zone, record)
}
return libdnsRecords
}
// Convert Record to libdns.Record
func libdnsToRecord(zone string, libdnsRecord libdns.Record) Record {
// for TTL
ttl := libdnsRecord.TTL.Seconds()
// for Value
recVals := strings.Split(libdnsRecord.Value, "\n")
valueRV := make([]RValue, len(recVals))
for i, recVal := range recVals {
if libdnsRecord.Type == "TXT" {
// if TXT, add to any preffix&suffix \"
recVal = strings.Trim(recVal, "\"")
valueRV[i] = RValue{Value: "\"" + recVal + "\""}
} else {
valueRV[i] = RValue{Value: recVal}
}
}
return Record{
ID: libdnsRecord.ID,
Type: libdnsRecord.Type,
Name: nameNormalizer(libdnsRecord.Name, zone),
Value: valueRV,
TTL: int(ttl),
}
}
// Get Selectel records
func (p *Provider) getSelectelRecords(ctx context.Context, zoneId string) ([]Record, error) {
recordB, err := p.makeApiRequest(ctx, httpMethods.get, "/zones/%s/rrset", nil, zoneId)
if err != nil {
return nil, err
}
recordset, err := deserialization[Recordset](recordB)
if err != nil {
return nil, err
}
return recordset.Records, nil
}
// Update Selectel record
func (p *Provider) updateSelectelRecord(ctx context.Context, zone string, zoneId string, record Record) (Record, error) {
body, err := json.Marshal(record)
if err != nil {
return Record{}, err
}
_, err = p.makeApiRequest(ctx, httpMethods.patch, "/zones/%s/rrset/%s", bytes.NewReader(body), zoneId, record.ID)
if err != nil {
return Record{}, err
}
return record, nil
}
// Normalize name in zone namespace
//
// test => test.zone.
// test.zone => test.zone.
// test.zone. => test.zone.
// test.subzone => test.subzone.zone.
// ...
func nameNormalizer(name string, zone string) string {
name = strings.TrimSuffix(name, ".")
zone = strings.TrimSuffix(zone, ".")
if strings.HasSuffix(name, "."+zone) {
return name + "."
}
return name + "." + zone + "."
}
// Check if an element with Id == id || (Name == name && Type == type) exists
func idFromRecordsByLibRecord(records []Record, libRecord libdns.Record, zone string) (string, bool) {
nameNorm := nameNormalizer(libRecord.Name, zone)
for _, record := range records {
recordNameNorm := nameNormalizer(record.Name, zone)
if libRecord.ID == record.ID || (recordNameNorm == nameNorm && libRecord.Type == record.Type) {
return record.ID ,true // Element found
}
}
return "", false // Element not found
}
// // map []Record to []libdns.Record
// func maplibdnsToRecord(libdnsRecords []libdns.Record) []Record {
// records := make([]Record, len(libdnsRecords))
// for i, record := range libdnsRecords {
// records[i] = libdnsToRecord(record)
// }
// return records
// }