1package autodns 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "path" 10 "strconv" 11) 12 13const ( 14 defaultEndpoint = "https://api.autodns.com/v1/" 15) 16 17type ResponseMessage struct { 18 Text string `json:"text"` 19 Messages []string `json:"messages"` 20 Objects []string `json:"objects"` 21 Code string `json:"code"` 22 Status string `json:"status"` 23} 24 25type ResponseStatus struct { 26 Code string `json:"code"` 27 Text string `json:"text"` 28 Type string `json:"type"` 29} 30 31type ResponseObject struct { 32 Type string `json:"type"` 33 Value string `json:"value"` 34 Summary int32 `json:"summary"` 35 Data string 36} 37 38type DataZoneResponse struct { 39 STID string `json:"stid"` 40 CTID string `json:"ctid"` 41 Messages []*ResponseMessage `json:"messages"` 42 Status *ResponseStatus `json:"status"` 43 Object interface{} `json:"object"` 44 Data []*Zone `json:"data"` 45} 46 47// ResourceRecord holds a resource record. 48type ResourceRecord struct { 49 Name string `json:"name"` 50 TTL int64 `json:"ttl"` 51 Type string `json:"type"` 52 Value string `json:"value"` 53 Pref int32 `json:"pref,omitempty"` 54} 55 56// Zone is an autodns zone record with all for us relevant fields. 57type Zone struct { 58 Name string `json:"origin"` 59 ResourceRecords []*ResourceRecord `json:"resourceRecords"` 60 Action string `json:"action"` 61 VirtualNameServer string `json:"virtualNameServer"` 62} 63 64type ZoneStream struct { 65 Adds []*ResourceRecord `json:"adds"` 66 Removes []*ResourceRecord `json:"rems"` 67} 68 69func (d *DNSProvider) addTxtRecord(domain string, records []*ResourceRecord) (*Zone, error) { 70 zoneStream := &ZoneStream{Adds: records} 71 72 return d.makeZoneUpdateRequest(zoneStream, domain) 73} 74 75func (d *DNSProvider) removeTXTRecord(domain string, records []*ResourceRecord) error { 76 zoneStream := &ZoneStream{Removes: records} 77 78 _, err := d.makeZoneUpdateRequest(zoneStream, domain) 79 return err 80} 81 82func (d *DNSProvider) makeZoneUpdateRequest(zoneStream *ZoneStream, domain string) (*Zone, error) { 83 reqBody := &bytes.Buffer{} 84 if err := json.NewEncoder(reqBody).Encode(zoneStream); err != nil { 85 return nil, err 86 } 87 88 req, err := d.makeRequest(http.MethodPost, path.Join("zone", domain, "_stream"), reqBody) 89 if err != nil { 90 return nil, err 91 } 92 93 var resp *Zone 94 if err := d.sendRequest(req, &resp); err != nil { 95 return nil, err 96 } 97 return resp, nil 98} 99 100func (d *DNSProvider) makeRequest(method, resource string, body io.Reader) (*http.Request, error) { 101 uri, err := d.config.Endpoint.Parse(resource) 102 if err != nil { 103 return nil, err 104 } 105 106 req, err := http.NewRequest(method, uri.String(), body) 107 if err != nil { 108 return nil, err 109 } 110 111 req.Header.Set("Content-Type", "application/json") 112 req.Header.Set("X-Domainrobot-Context", strconv.Itoa(d.config.Context)) 113 req.SetBasicAuth(d.config.Username, d.config.Password) 114 115 return req, nil 116} 117 118func (d *DNSProvider) sendRequest(req *http.Request, result interface{}) error { 119 resp, err := d.config.HTTPClient.Do(req) 120 if err != nil { 121 return err 122 } 123 124 if err = checkResponse(resp); err != nil { 125 return err 126 } 127 128 defer func() { _ = resp.Body.Close() }() 129 130 if result == nil { 131 return nil 132 } 133 134 raw, err := io.ReadAll(resp.Body) 135 if err != nil { 136 return err 137 } 138 139 err = json.Unmarshal(raw, result) 140 if err != nil { 141 return fmt.Errorf("unmarshaling %T error [status code=%d]: %w: %s", result, resp.StatusCode, err, string(raw)) 142 } 143 return err 144} 145 146func checkResponse(resp *http.Response) error { 147 if resp.StatusCode < http.StatusBadRequest { 148 return nil 149 } 150 151 if resp.Body == nil { 152 return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode) 153 } 154 155 defer func() { _ = resp.Body.Close() }() 156 157 raw, err := io.ReadAll(resp.Body) 158 if err != nil { 159 return fmt.Errorf("unable to read body: status code=%d, error=%w", resp.StatusCode, err) 160 } 161 162 return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw)) 163} 164