mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
migrate code for github
This commit is contained in:
306
providers/cloudflare/cloudflareProvider.go
Normal file
306
providers/cloudflare/cloudflareProvider.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"github.com/StackExchange/dnscontrol/transform"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Cloudflare APi DNS provider:
|
||||
|
||||
Info required in `creds.json`:
|
||||
- apikey
|
||||
- apiuser
|
||||
|
||||
Record level metadata availible:
|
||||
- cloudflare_proxy ("true" or "false")
|
||||
|
||||
Domain level metadata availible:
|
||||
- cloudflare_proxy_default ("true" or "false")
|
||||
|
||||
Provider level metadata availible:
|
||||
- ip_conversions
|
||||
- secret_ips
|
||||
*/
|
||||
|
||||
type CloudflareApi struct {
|
||||
ApiKey string `json:"apikey"`
|
||||
ApiUser string `json:"apiuser"`
|
||||
domainIndex map[string]string
|
||||
nameservers map[string][]*models.Nameserver
|
||||
ipConversions []transform.IpConversion
|
||||
secretIPs []net.IP
|
||||
ignoredLabels []string
|
||||
}
|
||||
|
||||
func labelMatches(label string, matches []string) bool {
|
||||
//log.Printf("DEBUG: labelMatches(%#v, %#v)\n", label, matches)
|
||||
for _, tst := range matches {
|
||||
if label == tst {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
if c.domainIndex == nil {
|
||||
if err := c.fetchDomainList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
id, ok := c.domainIndex[dc.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s not listed in zones for cloudflare account", dc.Name)
|
||||
}
|
||||
|
||||
dc.Nameservers = c.nameservers[dc.Name]
|
||||
if err := c.preprocessConfig(dc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records, err := c.getRecordsForDomain(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//for _, rec := range records {
|
||||
for i := len(records) - 1; i >= 0; i-- {
|
||||
rec := records[i]
|
||||
// Delete ignore labels
|
||||
if labelMatches(dnsutil.TrimDomainName(rec.(*cfRecord).Name, dc.Name), c.ignoredLabels) {
|
||||
fmt.Printf("ignored_label: %s\n", rec.(*cfRecord).Name)
|
||||
records = append(records[:i], records[i+1:]...)
|
||||
}
|
||||
//normalize cname,mx,ns records with dots to be consistent with our config format.
|
||||
t := rec.(*cfRecord).Type
|
||||
if t == "CNAME" || t == "MX" || t == "NS" {
|
||||
rec.(*cfRecord).Content = dnsutil.AddOrigin(rec.(*cfRecord).Content+".", dc.Name)
|
||||
}
|
||||
}
|
||||
|
||||
expectedRecords := make([]diff.Record, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if labelMatches(rec.Name, c.ignoredLabels) {
|
||||
log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.Name, c.ignoredLabels)
|
||||
// Since we log.Fatalf, we don't need to be clean here.
|
||||
}
|
||||
expectedRecords = append(expectedRecords, recordWrapper{rec})
|
||||
}
|
||||
_, create, del, mod := diff.IncrementalDiff(records, expectedRecords)
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, d := range del {
|
||||
corrections = append(corrections, c.deleteRec(d.Existing.(*cfRecord), id))
|
||||
}
|
||||
for _, d := range create {
|
||||
corrections = append(corrections, c.createRec(d.Desired.(recordWrapper).RecordConfig, id)...)
|
||||
}
|
||||
|
||||
for _, d := range mod {
|
||||
e, rec := d.Existing.(*cfRecord), d.Desired.(recordWrapper)
|
||||
proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: fmt.Sprintf("MODIFY record %s %s: (%s %s) => (%s %s)", rec.Name, rec.Type, e.Content, e.GetComparisionData(), rec.Target, rec.GetComparisionData()),
|
||||
F: func() error { return c.modifyRecord(id, e.ID, proxy, rec.RecordConfig) },
|
||||
})
|
||||
}
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
const (
|
||||
metaProxy = "cloudflare_proxy"
|
||||
metaProxyDefault = metaProxy + "_default"
|
||||
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
|
||||
metaIPConversions = "ip_conversions" // TODO(tlim): Rename to obscure_rules.
|
||||
metaSecretIPs = "secret_ips" // TODO(tlim): Rename to obscured_cidrs.
|
||||
)
|
||||
|
||||
func checkProxyVal(v string) (string, error) {
|
||||
v = strings.ToLower(v)
|
||||
if v != "on" && v != "off" && v != "full" {
|
||||
return "", fmt.Errorf("Bad metadata value for cloudflare_proxy: '%s'. Use on/off/full", v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
|
||||
|
||||
// Determine the default proxy setting.
|
||||
var defProxy string
|
||||
var err error
|
||||
if defProxy = dc.Metadata[metaProxyDefault]; defProxy == "" {
|
||||
defProxy = "off"
|
||||
} else {
|
||||
defProxy, err = checkProxyVal(defProxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the proxy setting for each record.
|
||||
// A and CNAMEs: Validate. If null, set to default.
|
||||
// else: Make sure it wasn't set. Set to default.
|
||||
for _, rec := range dc.Records {
|
||||
if rec.Type != "A" && rec.Type != "CNAME" && rec.Type != "AAAA" {
|
||||
if rec.Metadata[metaProxy] != "" {
|
||||
return fmt.Errorf("cloudflare_proxy set on %v record: %#v cloudflare_proxy=%#v", rec.Type, rec.Name, rec.Metadata[metaProxy])
|
||||
}
|
||||
// Force it to off.
|
||||
rec.Metadata[metaProxy] = "off"
|
||||
} else {
|
||||
if val := rec.Metadata[metaProxy]; val == "" {
|
||||
rec.Metadata[metaProxy] = defProxy
|
||||
} else {
|
||||
val, err := checkProxyVal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rec.Metadata[metaProxy] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look for ip conversions and transform records
|
||||
for _, rec := range dc.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = 1
|
||||
}
|
||||
if rec.Type != "A" {
|
||||
continue
|
||||
}
|
||||
//only transform "full"
|
||||
if rec.Metadata[metaProxy] != "full" {
|
||||
continue
|
||||
}
|
||||
ip := net.ParseIP(rec.Target)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("%s is not a valid ip address", rec.Target)
|
||||
}
|
||||
newIP, err := transform.TransformIP(ip, c.ipConversions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rec.Metadata[metaOriginalIP] = rec.Target
|
||||
rec.Target = newIP.String()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
api := &CloudflareApi{}
|
||||
api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
|
||||
// check api keys from creds json file
|
||||
if api.ApiKey == "" || api.ApiUser == "" {
|
||||
return nil, fmt.Errorf("Cloudflare apikey and apiuser must be provided.")
|
||||
}
|
||||
|
||||
if len(metadata) > 0 {
|
||||
parsedMeta := &struct {
|
||||
IPConversions string `json:"ip_conversions"`
|
||||
SecretIps []interface{} `json:"secret_ips"`
|
||||
IgnoredLabels []string `json:"ignored_labels"`
|
||||
}{}
|
||||
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ignored_labels:
|
||||
for _, l := range parsedMeta.IgnoredLabels {
|
||||
api.ignoredLabels = append(api.ignoredLabels, l)
|
||||
}
|
||||
// parse provider level metadata
|
||||
api.ipConversions, err = transform.DecodeTransformTable(parsedMeta.IPConversions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := []net.IP{}
|
||||
for _, ipStr := range parsedMeta.SecretIps {
|
||||
var ip net.IP
|
||||
if ip, err = models.InterfaceToIP(ipStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
api.secretIPs = ips
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare)
|
||||
}
|
||||
|
||||
// Used on the "existing" records.
|
||||
type cfRecord struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Proxiable bool `json:"proxiable"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TTL int `json:"ttl"`
|
||||
Locked bool `json:"locked"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
ZoneName string `json:"zone_name"`
|
||||
CreatedOn time.Time `json:"created_on"`
|
||||
ModifiedOn time.Time `json:"modified_on"`
|
||||
Data interface{} `json:"data"`
|
||||
Priority int `json:"priority"`
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetType() string {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetContent() string {
|
||||
return c.Content
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if c.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %d ", c.Priority)
|
||||
}
|
||||
proxy := ""
|
||||
if c.Type == "A" || c.Type == "CNAME" || c.Type == "AAAA" {
|
||||
proxy = fmt.Sprintf(" proxy=%v ", c.Proxied)
|
||||
}
|
||||
return fmt.Sprintf("%d%s%s", c.TTL, mxPrio, proxy)
|
||||
}
|
||||
|
||||
// Used on the "expected" records.
|
||||
type recordWrapper struct {
|
||||
*models.RecordConfig
|
||||
}
|
||||
|
||||
func (c recordWrapper) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if c.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %d ", c.Priority)
|
||||
}
|
||||
proxy := ""
|
||||
if c.Type == "A" || c.Type == "AAAA" || c.Type == "CNAME" {
|
||||
proxy = fmt.Sprintf(" proxy=%v ", c.Metadata[metaProxy] != "off")
|
||||
}
|
||||
|
||||
ttl := c.TTL
|
||||
if ttl == 0 {
|
||||
ttl = 1
|
||||
}
|
||||
return fmt.Sprintf("%d%s%s", ttl, mxPrio, proxy)
|
||||
}
|
116
providers/cloudflare/preprocess_test.go
Normal file
116
providers/cloudflare/preprocess_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/transform"
|
||||
)
|
||||
|
||||
func newDomainConfig() *models.DomainConfig {
|
||||
return &models.DomainConfig{
|
||||
Name: "test.com",
|
||||
Records: []*models.RecordConfig{},
|
||||
Metadata: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreprocess_BoolValidation(t *testing.T) {
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "on"}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "fUll"}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "Off"}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "off"}})
|
||||
err := cf.preprocessConfig(domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"on", "full", "off", "off", "off"}
|
||||
// make sure only "on" or "off", and "full" are actually set
|
||||
for i, rec := range domain.Records {
|
||||
if rec.Metadata[metaProxy] != expected[i] {
|
||||
t.Fatalf("At index %d: expect '%s' but found '%s'", i, expected[i], rec.Metadata[metaProxy])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreprocess_BoolValidation_Fails(t *testing.T) {
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Metadata: map[string]string{metaProxy: "true"}})
|
||||
err := cf.preprocessConfig(domain)
|
||||
if err == nil {
|
||||
t.Fatal("Expected validation error, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreprocess_DefaultProxy(t *testing.T) {
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
domain.Metadata[metaProxyDefault] = "full"
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "on"}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "off"}})
|
||||
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{}})
|
||||
err := cf.preprocessConfig(domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"on", "off", "full"}
|
||||
for i, rec := range domain.Records {
|
||||
if rec.Metadata[metaProxy] != expected[i] {
|
||||
t.Fatalf("At index %d: expect '%s' but found '%s'", i, expected[i], rec.Metadata[metaProxy])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreprocess_DefaultProxy_Validation(t *testing.T) {
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
domain.Metadata[metaProxyDefault] = "true"
|
||||
err := cf.preprocessConfig(domain)
|
||||
if err == nil {
|
||||
t.Fatal("Expected validation error, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIpRewriting(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Given, Expected string
|
||||
Proxy string
|
||||
}{
|
||||
//outside of range
|
||||
{"5.5.5.5", "5.5.5.5", "full"},
|
||||
{"5.5.5.5", "5.5.5.5", "on"},
|
||||
// inside range, but not proxied
|
||||
{"1.2.3.4", "1.2.3.4", "on"},
|
||||
//inside range and proxied
|
||||
{"1.2.3.4", "255.255.255.4", "full"},
|
||||
}
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
cf.ipConversions = []transform.IpConversion{{net.ParseIP("1.2.3.0"), net.ParseIP("1.2.3.40"), net.ParseIP("255.255.255.0"), nil}}
|
||||
for _, tst := range tests {
|
||||
rec := &models.RecordConfig{Type: "A", Target: tst.Given, Metadata: map[string]string{metaProxy: tst.Proxy}}
|
||||
domain.Records = append(domain.Records, rec)
|
||||
}
|
||||
err := cf.preprocessConfig(domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, tst := range tests {
|
||||
rec := domain.Records[i]
|
||||
if rec.Target != tst.Expected {
|
||||
t.Fatalf("At index %d, expected target of %s, but found %s.", i, tst.Expected, rec.Target)
|
||||
}
|
||||
if tst.Proxy == "full" && tst.Given != tst.Expected && rec.Metadata[metaOriginalIP] != tst.Given {
|
||||
t.Fatalf("At index %d, expected original_ip to be set", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCnameValidation(t *testing.T) {
|
||||
|
||||
}
|
251
providers/cloudflare/rest.go
Normal file
251
providers/cloudflare/rest.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://api.cloudflare.com/client/v4/"
|
||||
zonesURL = baseURL + "zones/"
|
||||
recordsURL = zonesURL + "%s/dns_records/"
|
||||
singleRecordURL = recordsURL + "%s"
|
||||
)
|
||||
|
||||
// get list of domains for account. Cache so the ids can be looked up from domain name
|
||||
func (c *CloudflareApi) fetchDomainList() error {
|
||||
c.domainIndex = map[string]string{}
|
||||
c.nameservers = map[string][]*models.Nameserver{}
|
||||
page := 1
|
||||
for {
|
||||
zr := &zoneResponse{}
|
||||
url := fmt.Sprintf("%s?page=%d&per_page=50", zonesURL, page)
|
||||
if err := c.get(url, zr); err != nil {
|
||||
return fmt.Errorf("Error fetching domain list from cloudflare: %s", err)
|
||||
}
|
||||
if !zr.Success {
|
||||
return fmt.Errorf("Error fetching domain list from cloudflare: %s", stringifyErrors(zr.Errors))
|
||||
}
|
||||
for _, zone := range zr.Result {
|
||||
c.domainIndex[zone.Name] = zone.ID
|
||||
for _, ns := range zone.Nameservers {
|
||||
c.nameservers[zone.Name] = append(c.nameservers[zone.Name], &models.Nameserver{Name: ns})
|
||||
}
|
||||
}
|
||||
ri := zr.ResultInfo
|
||||
if len(zr.Result) == 0 || ri.Page*ri.PerPage >= ri.TotalCount {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get all records for a domain
|
||||
func (c *CloudflareApi) getRecordsForDomain(id string) ([]diff.Record, error) {
|
||||
url := fmt.Sprintf(recordsURL, id)
|
||||
page := 1
|
||||
records := []diff.Record{}
|
||||
for {
|
||||
reqURL := fmt.Sprintf("%s?page=%d&per_page=100", url, page)
|
||||
var data recordsResponse
|
||||
if err := c.get(reqURL, &data); err != nil {
|
||||
return nil, fmt.Errorf("Error fetching record list from cloudflare: %s", err)
|
||||
}
|
||||
if !data.Success {
|
||||
return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors))
|
||||
}
|
||||
for _, rec := range data.Result {
|
||||
records = append(records, rec)
|
||||
}
|
||||
ri := data.ResultInfo
|
||||
if len(data.Result) == 0 || ri.Page*ri.PerPage >= ri.TotalCount {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// create a correction to delete a record
|
||||
func (c *CloudflareApi) deleteRec(rec *cfRecord, domainID string) *models.Correction {
|
||||
return &models.Correction{
|
||||
Msg: fmt.Sprintf("DELETE record: %s %s %d %s (id=%s)", rec.Name, rec.Type, rec.TTL, rec.Content, rec.ID),
|
||||
F: func() error {
|
||||
endpoint := fmt.Sprintf(singleRecordURL, domainID, rec.ID)
|
||||
req, err := http.NewRequest("DELETE", endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
_, err = handleActionResponse(http.DefaultClient.Do(req))
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*models.Correction {
|
||||
type createRecord struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Priority uint16 `json:"priority"`
|
||||
}
|
||||
var id string
|
||||
content := rec.Target
|
||||
if rec.Metadata[metaOriginalIP] != "" {
|
||||
content = rec.Metadata[metaOriginalIP]
|
||||
}
|
||||
prio := ""
|
||||
if rec.Type == "MX" {
|
||||
prio = fmt.Sprintf(" %d ", rec.Priority)
|
||||
}
|
||||
arr := []*models.Correction{{
|
||||
Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.Name, rec.Type, rec.TTL, prio, content),
|
||||
F: func() error {
|
||||
|
||||
cf := &createRecord{
|
||||
Name: rec.Name,
|
||||
Type: rec.Type,
|
||||
TTL: rec.TTL,
|
||||
Content: content,
|
||||
Priority: rec.Priority,
|
||||
}
|
||||
endpoint := fmt.Sprintf(recordsURL, domainID)
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buf)
|
||||
if err := encoder.Encode(cf); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", endpoint, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
id, err = handleActionResponse(http.DefaultClient.Do(req))
|
||||
return err
|
||||
},
|
||||
}}
|
||||
if rec.Metadata[metaProxy] != "off" {
|
||||
arr = append(arr, &models.Correction{
|
||||
Msg: fmt.Sprintf("ACTIVATE PROXY for new record %s %s %d %s", rec.Name, rec.Type, rec.TTL, rec.Target),
|
||||
F: func() error { return c.modifyRecord(domainID, id, true, rec) },
|
||||
})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error {
|
||||
if domainID == "" || recID == "" {
|
||||
return fmt.Errorf("Cannot modify record if domain or record id are empty.")
|
||||
}
|
||||
type record struct {
|
||||
ID string `json:"id"`
|
||||
Proxied bool `json:"proxied"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Priority uint16 `json:"priority"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
}
|
||||
r := record{recID, proxied, rec.Name, rec.Type, rec.Target, rec.Priority, rec.TTL}
|
||||
endpoint := fmt.Sprintf(singleRecordURL, domainID, recID)
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buf)
|
||||
if err := encoder.Encode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", endpoint, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
_, err = handleActionResponse(http.DefaultClient.Do(req))
|
||||
return err
|
||||
}
|
||||
|
||||
// common error handling for all action responses
|
||||
func handleActionResponse(resp *http.Response, err error) (id string, e error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
result := &basicResponse{}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
if err = decoder.Decode(result); err != nil {
|
||||
return "", fmt.Errorf("Unknown error. Status code: %d", resp.StatusCode)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf(stringifyErrors(result.Errors))
|
||||
}
|
||||
return result.Result.ID, nil
|
||||
}
|
||||
|
||||
func (c *CloudflareApi) setHeaders(req *http.Request) {
|
||||
req.Header.Set("X-Auth-Key", c.ApiKey)
|
||||
req.Header.Set("X-Auth-Email", c.ApiUser)
|
||||
}
|
||||
|
||||
// generic get handler. makes request and unmarshalls response to given interface
|
||||
func (c *CloudflareApi) get(endpoint string, target interface{}) error {
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setHeaders(req)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("Bad status code from cloudflare: %d not 200.", resp.StatusCode)
|
||||
}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
return decoder.Decode(target)
|
||||
}
|
||||
|
||||
func stringifyErrors(errors []interface{}) string {
|
||||
dat, err := json.Marshal(errors)
|
||||
if err != nil {
|
||||
return "???"
|
||||
}
|
||||
return string(dat)
|
||||
}
|
||||
|
||||
type recordsResponse struct {
|
||||
basicResponse
|
||||
Result []*cfRecord `json:"result"`
|
||||
ResultInfo pagingInfo `json:"result_info"`
|
||||
}
|
||||
type basicResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Errors []interface{} `json:"errors"`
|
||||
Messages []interface{} `json:"messages"`
|
||||
Result struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type zoneResponse struct {
|
||||
basicResponse
|
||||
Result []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Nameservers []string `json:"name_servers"`
|
||||
} `json:"result"`
|
||||
ResultInfo pagingInfo `json:"result_info"`
|
||||
}
|
||||
|
||||
type pagingInfo struct {
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
Count int `json:"count"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
Reference in New Issue
Block a user