blob: 0af0c6501cf943a1f3ddd665dc8e70d5f4e3a427 [file] [log] [blame]
Giorgi Lekveishvili0048a782023-06-20 18:32:21 +04001package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io/ioutil"
8 "net/http"
9 "net/http/httputil"
10 "time"
11)
12
13const (
14 GandiLiveDnsBaseUrl = "https://api.gandi.net/v5/livedns"
15)
16
17type GandiClient struct {
18 apiKey string
19 dumpRequestResponse bool
20}
21
22type GandiRRSet struct {
23 Type string `json:"rrset_type"`
24 TTL int `json:"rrset_ttl"`
25 Name string `json:"rrset_name"`
26 Values []string `json:"rrset_values"`
27}
28
29type GandiRRSetValues struct {
30 TTL int `json:"rrset_ttl"`
31 Values []string `json:"rrset_values"`
32}
33
34func NewGandiClient(apiKey string) *GandiClient {
35 return &GandiClient{
36 apiKey: apiKey,
37 dumpRequestResponse: false,
38 }
39}
40
41func (c *GandiClient) gandiRecordsUrl(domain string) string {
42 return fmt.Sprintf("%s/domains/%s/records", GandiLiveDnsBaseUrl, domain)
43}
44
45func (c *GandiClient) doRequest(req *http.Request, readResponseBody bool) (int, []byte, error) {
46 if c.dumpRequestResponse {
47 dump, _ := httputil.DumpRequest(req, true)
48 fmt.Printf("Request: %q\n", dump)
49 }
50
51 req.Header.Set("Authorization", fmt.Sprintf("Apikey %s", c.apiKey))
52 client := http.Client{
53 Timeout: 30 * time.Second,
54 }
55
56 res, err := client.Do(req)
57 if err != nil {
58 return 0, nil, err
59 }
60
61 if c.dumpRequestResponse {
62 dump, _ := httputil.DumpResponse(res, true)
63 fmt.Printf("Response: %q\n", dump)
64 }
65
66 if res.StatusCode == http.StatusOK && readResponseBody {
67 data, err := ioutil.ReadAll(res.Body)
68 if err != nil {
69 return 0, nil, err
70 }
71 return res.StatusCode, data, nil
72 }
73
74 return res.StatusCode, nil, nil
75}
76
77func (c *GandiClient) HasTxtRecord(domain *string, name *string) (bool, error) {
78 // curl -X GET -H "Content-Type: application/json" \
79 // -H "Authorization: Apikey $APIKEY" \
80 // https://api.gandi.net/v5/livedns/domains/<DOMAIN>/records/<NAME>/<TYPE>
81 url := fmt.Sprintf("%s/%s/TXT", c.gandiRecordsUrl(*domain), *name)
82 req, err := http.NewRequest("GET", url, nil)
83 if err != nil {
84 return false, err
85 }
86
87 status, _, err := c.doRequest(req, false)
88 if err != nil {
89 return false, err
90 }
91
92 if status == http.StatusNotFound {
93 return false, nil
94 } else if status == http.StatusOK {
95 // Maybe parse response body here to really ensure that the record is present
96 return true, nil
97 } else {
98 return false, fmt.Errorf("unexpected HTTP status: %d", status)
99 }
100}
101
102func (c *GandiClient) CreateTxtRecord(domain *string, name *string, value *string, ttl int) error {
103 // curl -X POST -H "Content-Type: application/json" \
104 // -H "Authorization: Apikey $APIKEY" \
105 // -d '{"rrset_name": "<NAME>", "rrset_type": "<TYPE>", "rrset_ttl": 10800, "rrset_values": ["<VALUE>"]}' \
106 // https://api.gandi.net/v5/livedns/domains/<DOMAIN>/records
107 rrs := GandiRRSet{Name: *name, Type: "TXT", TTL: ttl, Values: []string{*value}}
108 body, err := json.Marshal(rrs)
109 if err != nil {
110 return fmt.Errorf("cannot marshall to json: %v", err)
111 }
112
113 url := c.gandiRecordsUrl(*domain)
114 req, err := http.NewRequest("POST", url, bytes.NewReader(body))
115 if err != nil {
116 return err
117 }
118
119 req.Header.Set("Content-Type", "application/json")
120
121 status, _, err := c.doRequest(req, false)
122 if err != nil {
123 return err
124 }
125
126 if status != http.StatusCreated && status != http.StatusOK {
127 return fmt.Errorf("failed creating TXT record: %v", err)
128 }
129
130 return nil
131}
132
133func (c *GandiClient) UpdateTxtRecord(domain *string, name *string, value *string, ttl int) error {
134 // curl -X PUT -H "Content-Type: application/json" \
135 // -H "Authorization: Apikey $APIKEY" \
136 // -d '{"rrset_ttl": 10800, "rrset_values":["<VALUE>"]}' \
137 // https://api.gandi.net/v5/livedns/domains/<DOMAIN>/records/<NAME>/<TYPE>
138 rrs := GandiRRSetValues{TTL: ttl, Values: []string{*value}}
139 body, err := json.Marshal(rrs)
140 if err != nil {
141 return fmt.Errorf("cannot marshall to json: %v", err)
142 }
143
144 url := fmt.Sprintf("%s/%s/TXT", c.gandiRecordsUrl(*domain), *name)
145 req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
146 if err != nil {
147 return err
148 }
149
150 req.Header.Set("Content-Type", "application/json")
151
152 status, _, err := c.doRequest(req, false)
153 if err != nil {
154 return err
155 }
156
157 if status != http.StatusCreated && status != http.StatusOK {
158 return fmt.Errorf("failed updating TXT record: %v", err)
159 }
160
161 return nil
162}
163
164func (c *GandiClient) DeleteTxtRecord(domain *string, name *string) error {
165 // curl -X DELETE -H "Content-Type: application/json" \
166 // -H "Authorization: Apikey $APIKEY" \
167 // https://api.gandi.net/v5/livedns/domains/<DOMAIN>/records/<NAME>/<TYPE>
168 url := fmt.Sprintf("%s/%s/TXT", c.gandiRecordsUrl(*domain), *name)
169 req, err := http.NewRequest("DELETE", url, nil)
170 if err != nil {
171 return err
172 }
173
174 status, _, err := c.doRequest(req, false)
175 if err != nil {
176 return err
177 }
178
179 if status != http.StatusOK && status != http.StatusNoContent {
180 return fmt.Errorf("failed deleting TXT record: %v", err)
181 }
182
183 return nil
184}