151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
package cloudflare
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/libdns/libdns"
|
|
)
|
|
|
|
func (p *Provider) createRecord(ctx context.Context, zoneInfo cfZone, record libdns.Record) (cfDNSRecord, error) {
|
|
jsonBytes, err := json.Marshal(cloudflareRecord(record))
|
|
if err != nil {
|
|
return cfDNSRecord{}, err
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records", baseURL, zoneInfo.ID)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewReader(jsonBytes))
|
|
if err != nil {
|
|
return cfDNSRecord{}, err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
var result cfDNSRecord
|
|
_, err = p.doAPIRequest(req, &result)
|
|
if err != nil {
|
|
return cfDNSRecord{}, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// updateRecord updates a DNS record. oldRec must have both an ID and zone ID.
|
|
// Only the non-empty fields in newRec will be changed.
|
|
func (p *Provider) updateRecord(ctx context.Context, oldRec, newRec cfDNSRecord) (cfDNSRecord, error) {
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records/%s", baseURL, oldRec.ZoneID, oldRec.ID)
|
|
jsonBytes, err := json.Marshal(newRec)
|
|
if err != nil {
|
|
return cfDNSRecord{}, err
|
|
}
|
|
|
|
// PATCH changes only the populated fields; PUT resets Type, Name, Content, and TTL even if empty
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, reqURL, bytes.NewReader(jsonBytes))
|
|
if err != nil {
|
|
return cfDNSRecord{}, err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
var result cfDNSRecord
|
|
_, err = p.doAPIRequest(req, &result)
|
|
return result, err
|
|
}
|
|
|
|
func (p *Provider) getDNSRecords(ctx context.Context, zoneInfo cfZone, rec libdns.Record, matchContent bool) ([]cfDNSRecord, error) {
|
|
qs := make(url.Values)
|
|
qs.Set("type", rec.Type)
|
|
qs.Set("name", libdns.AbsoluteName(rec.Name, zoneInfo.Name))
|
|
if matchContent {
|
|
qs.Set("content", rec.Value)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/zones/%s/dns_records?%s", baseURL, zoneInfo.ID, qs.Encode())
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var results []cfDNSRecord
|
|
_, err = p.doAPIRequest(req, &results)
|
|
return results, err
|
|
}
|
|
|
|
func (p *Provider) getZoneInfo(ctx context.Context, zoneName string) (cfZone, error) {
|
|
p.zonesMu.Lock()
|
|
defer p.zonesMu.Unlock()
|
|
|
|
// if we already got the zone info, reuse it
|
|
if p.zones == nil {
|
|
p.zones = make(map[string]cfZone)
|
|
}
|
|
if zone, ok := p.zones[zoneName]; ok {
|
|
return zone, nil
|
|
}
|
|
|
|
qs := make(url.Values)
|
|
qs.Set("name", zoneName)
|
|
reqURL := fmt.Sprintf("%s/zones?%s", baseURL, qs.Encode())
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
return cfZone{}, err
|
|
}
|
|
|
|
var zones []cfZone
|
|
_, err = p.doAPIRequest(req, &zones)
|
|
if err != nil {
|
|
return cfZone{}, err
|
|
}
|
|
if len(zones) != 1 {
|
|
return cfZone{}, fmt.Errorf("expected 1 zone, got %d for %s", len(zones), zoneName)
|
|
}
|
|
|
|
// cache this zone for possible reuse
|
|
p.zones[zoneName] = zones[0]
|
|
|
|
return zones[0], nil
|
|
}
|
|
|
|
// doAPIRequest authenticates the request req and does the round trip. It returns
|
|
// the decoded response from Cloudflare if successful; otherwise it returns an
|
|
// error including error information from the API if applicable. If result is a
|
|
// non-nil pointer, the result field from the API response will be decoded into
|
|
// it for convenience.
|
|
func (p *Provider) doAPIRequest(req *http.Request, result interface{}) (cfResponse, error) {
|
|
req.Header.Set("Authorization", "Bearer "+p.APIToken)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return cfResponse{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var respData cfResponse
|
|
err = json.NewDecoder(resp.Body).Decode(&respData)
|
|
if err != nil {
|
|
return cfResponse{}, err
|
|
}
|
|
|
|
if resp.StatusCode >= 400 {
|
|
return cfResponse{}, fmt.Errorf("got error status: HTTP %d: %+v", resp.StatusCode, respData.Errors)
|
|
}
|
|
if len(respData.Errors) > 0 {
|
|
return cfResponse{}, fmt.Errorf("got errors: HTTP %d: %+v", resp.StatusCode, respData.Errors)
|
|
}
|
|
|
|
if len(respData.Result) > 0 && result != nil {
|
|
err = json.Unmarshal(respData.Result, result)
|
|
if err != nil {
|
|
return cfResponse{}, err
|
|
}
|
|
respData.Result = nil
|
|
}
|
|
|
|
return respData, err
|
|
}
|
|
|
|
const baseURL = "https://api.cloudflare.com/client/v4"
|