mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Add SRV Record Type (#136)
* Add support for SRV records for NAMEDOTCOM and ROUTE53. * Improve docs * Rename RR() to ToRR(). * Rename RecordConfig Priority to MxPreference (affects json IR data)
This commit is contained in:
@ -33,4 +33,4 @@ var bind = NewDnsProvider('bind', 'BIND', {
|
|||||||
|
|
||||||
If you need to customize your SOA or NS records, you can do it with this setup.
|
If you need to customize your SOA or NS records, you can do it with this setup.
|
||||||
|
|
||||||
You can also provide a `-bindtree=directoryName` flag to change where the provider will look for and create zone files. The default is the `zones` directory where dnscontrol is run.
|
You can also provide a `-bindtree=directoryName` flag to change where the provider will look for and create zone files. The default is the `zones` directory where dnscontrol is run.
|
||||||
|
@ -119,7 +119,12 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !skipVal && i != *startIdx && len(corrections) == 0 {
|
if !skipVal && i != *startIdx && len(corrections) == 0 {
|
||||||
t.Fatalf("Expect changes for all tests, but got none")
|
if tst.Desc != "Empty" {
|
||||||
|
// There are "no corrections" if the last test was programatically
|
||||||
|
// skipped. We detect this (possibly inaccurately) by checking to
|
||||||
|
// see if .Desc is "Empty".
|
||||||
|
t.Fatalf("Expect changes for all tests, but got none")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, c := range corrections {
|
for _, c := range corrections {
|
||||||
if *verbose {
|
if *verbose {
|
||||||
@ -215,7 +220,7 @@ func ns(name, target string) *rec {
|
|||||||
|
|
||||||
func mx(name string, prio uint16, target string) *rec {
|
func mx(name string, prio uint16, target string) *rec {
|
||||||
r := makeRec(name, target, "MX")
|
r := makeRec(name, target, "MX")
|
||||||
r.Priority = prio
|
r.MxPreference = prio
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +228,14 @@ func ptr(name, target string) *rec {
|
|||||||
return makeRec(name, target, "PTR")
|
return makeRec(name, target, "PTR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func srv(name string, priority, weight, port uint16, target string) *rec {
|
||||||
|
r := makeRec(name, target, "SRV")
|
||||||
|
r.SrvPriority = priority
|
||||||
|
r.SrvWeight = weight
|
||||||
|
r.SrvPort = port
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func makeRec(name, target, typ string) *rec {
|
func makeRec(name, target, typ string) *rec {
|
||||||
return &rec{
|
return &rec{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -257,8 +270,8 @@ var tests = []*TestCase{
|
|||||||
tc("Change it", a("@", "1.2.3.4")),
|
tc("Change it", a("@", "1.2.3.4")),
|
||||||
tc("Add another", a("@", "1.2.3.4"), a("www", "1.2.3.4")),
|
tc("Add another", a("@", "1.2.3.4"), a("www", "1.2.3.4")),
|
||||||
tc("Add another(same name)", a("@", "1.2.3.4"), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
tc("Add another(same name)", a("@", "1.2.3.4"), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
||||||
tc("Change a ttl", a("@", "1.2.3.4").ttl(100), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
tc("Change a ttl", a("@", "1.2.3.4").ttl(1000), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
||||||
tc("Change single target from set", a("@", "1.2.3.4").ttl(100), a("www", "2.2.2.2"), a("www", "5.6.7.8")),
|
tc("Change single target from set", a("@", "1.2.3.4").ttl(1000), a("www", "2.2.2.2"), a("www", "5.6.7.8")),
|
||||||
tc("Change all ttls", a("@", "1.2.3.4").ttl(500), a("www", "2.2.2.2").ttl(400), a("www", "5.6.7.8").ttl(400)),
|
tc("Change all ttls", a("@", "1.2.3.4").ttl(500), a("www", "2.2.2.2").ttl(400), a("www", "5.6.7.8").ttl(400)),
|
||||||
tc("Delete one", a("@", "1.2.3.4").ttl(500), a("www", "5.6.7.8").ttl(400)),
|
tc("Delete one", a("@", "1.2.3.4").ttl(500), a("www", "5.6.7.8").ttl(400)),
|
||||||
tc("Add back and change ttl", a("www", "5.6.7.8").ttl(700), a("www", "1.2.3.4").ttl(700)),
|
tc("Add back and change ttl", a("www", "5.6.7.8").ttl(700), a("www", "1.2.3.4").ttl(700)),
|
||||||
@ -290,7 +303,7 @@ var tests = []*TestCase{
|
|||||||
tc("3 MX", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")),
|
tc("3 MX", mx("@", 5, "foo.com."), mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")),
|
||||||
tc("Delete one", mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")),
|
tc("Delete one", mx("@", 5, "foo2.com."), mx("@", 15, "foo3.com.")),
|
||||||
tc("Change to other name", mx("@", 5, "foo2.com."), mx("mail", 15, "foo3.com.")),
|
tc("Change to other name", mx("@", 5, "foo2.com."), mx("mail", 15, "foo3.com.")),
|
||||||
tc("Change Priority", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")),
|
tc("Change Preference", mx("@", 7, "foo2.com."), mx("mail", 15, "foo3.com.")),
|
||||||
|
|
||||||
//PTR
|
//PTR
|
||||||
tc("Empty"),
|
tc("Empty"),
|
||||||
@ -298,11 +311,22 @@ var tests = []*TestCase{
|
|||||||
tc("Modify PTR record", ptr("4", "bar.com.")),
|
tc("Modify PTR record", ptr("4", "bar.com.")),
|
||||||
|
|
||||||
//ALIAS
|
//ALIAS
|
||||||
tc("EMPTY"),
|
tc("Empty"),
|
||||||
tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias),
|
tc("ALIAS at root", alias("@", "foo.com.")).IfHasCapability(providers.CanUseAlias),
|
||||||
tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias),
|
tc("change it", alias("@", "foo2.com.")).IfHasCapability(providers.CanUseAlias),
|
||||||
tc("ALIAS at subdomain", alias("test", "foo.com.")).IfHasCapability(providers.CanUseAlias),
|
tc("ALIAS at subdomain", alias("test", "foo.com.")).IfHasCapability(providers.CanUseAlias),
|
||||||
|
|
||||||
|
//SRV
|
||||||
|
tc("Empty"),
|
||||||
|
tc("SRV record", srv("@", 5, 6, 7, "foo.com.")),
|
||||||
|
tc("Second SRV record, same prio", srv("@", 5, 6, 7, "foo.com."), srv("@", 5, 60, 70, "foo2.com.")),
|
||||||
|
tc("3 SRV", srv("@", 5, 6, 7, "foo.com."), srv("@", 5, 60, 70, "foo2.com."), srv("@", 15, 65, 75, "foo3.com.")),
|
||||||
|
tc("Delete one", srv("@", 5, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo3.com.")),
|
||||||
|
tc("Change Target", srv("@", 5, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")),
|
||||||
|
tc("Change Priority", srv("@", 52, 6, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")),
|
||||||
|
tc("Change Weight", srv("@", 52, 62, 7, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")),
|
||||||
|
tc("Change Port", srv("@", 52, 62, 72, "foo.com."), srv("@", 15, 65, 75, "foo4.com.")),
|
||||||
|
|
||||||
//TODO: in validation, check that everything is given in unicode. This case hurts too much.
|
//TODO: in validation, check that everything is given in unicode. This case hurts too much.
|
||||||
//tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")),
|
//tc("IDN pre-punycoded", cname("xn--o-0gab", "xn--o-0gab.xn--o-0gab.")),
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"token": "$DNSIMPLE_TOKEN"
|
"token": "$DNSIMPLE_TOKEN"
|
||||||
},
|
},
|
||||||
"GANDI": {
|
"GANDI": {
|
||||||
"COMMENT": "5: gandi does not accept ttls less than 300",
|
"COMMENT": "5: gandi does not accept TTLs less than 300",
|
||||||
"apikey": "$GANDI_KEY",
|
"apikey": "$GANDI_KEY",
|
||||||
"domain": "$GANDI_DOMAIN",
|
"domain": "$GANDI_DOMAIN",
|
||||||
"knownFailures": "5"
|
"knownFailures": "5"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
$TTL 300
|
$TTL 300
|
||||||
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017070632 3600 600 604800 1440
|
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017071945 3600 600 604800 1440
|
||||||
IN NS ns1.otherdomain.tld.
|
IN NS ns1.otherdomain.tld.
|
||||||
IN NS ns2.otherdomain.tld.
|
IN NS ns2.otherdomain.tld.
|
||||||
|
147
models/dns.go
147
models/dns.go
@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/pkg/transform"
|
"github.com/StackExchange/dnscontrol/pkg/transform"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -60,21 +61,30 @@ type DNSProviderConfig struct {
|
|||||||
// This is the FQDN version of Name.
|
// This is the FQDN version of Name.
|
||||||
// It should never have a trailiing ".".
|
// It should never have a trailiing ".".
|
||||||
type RecordConfig struct {
|
type RecordConfig struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"` // The short name. See below.
|
Name string `json:"name"` // The short name. See below.
|
||||||
Target string `json:"target"` // If a name, must end with "."
|
Target string `json:"target"` // If a name, must end with "."
|
||||||
TTL uint32 `json:"ttl,omitempty"`
|
TTL uint32 `json:"ttl,omitempty"`
|
||||||
Metadata map[string]string `json:"meta,omitempty"`
|
Metadata map[string]string `json:"meta,omitempty"`
|
||||||
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
||||||
Priority uint16 `json:"priority,omitempty"`
|
MxPreference uint16 `json:"mxpreference,omitempty"` // FIXME(tlim): Rename to MxPreference
|
||||||
|
SrvPriority uint16 `json:"srvpriority,omitempty"`
|
||||||
|
SrvWeight uint16 `json:"srvweight,omitempty"`
|
||||||
|
SrvPort uint16 `json:"srvport,omitempty"`
|
||||||
|
|
||||||
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
|
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordConfig) String() string {
|
func (r *RecordConfig) String() (content string) {
|
||||||
content := fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL)
|
switch r.Type {
|
||||||
if r.Type == "MX" {
|
case "A", "AAAA", "PTR":
|
||||||
content += fmt.Sprintf(" priority=%d", r.Priority)
|
content = fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL)
|
||||||
|
case "MX":
|
||||||
|
content = fmt.Sprintf(" priority=%d", r.MxPreference)
|
||||||
|
case "SOA":
|
||||||
|
content = fmt.Sprintf("%s %s %s %d", r.Type, r.Name, r.Target, r.TTL)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("rc.String rtype %v unimplemented", r.Type))
|
||||||
}
|
}
|
||||||
for k, v := range r.Metadata {
|
for k, v := range r.Metadata {
|
||||||
content += fmt.Sprintf(" %s=%s", k, v)
|
content += fmt.Sprintf(" %s=%s", k, v)
|
||||||
@ -82,52 +92,103 @@ func (r *RecordConfig) String() string {
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content combines Target and other fields into one string.
|
||||||
|
func (r *RecordConfig) Content() string {
|
||||||
|
|
||||||
|
// If this is a pseudo record, just return the target.
|
||||||
|
if _, ok := dns.StringToType[r.Type]; !ok {
|
||||||
|
return r.Target
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cheat by converting to a dns.RR and use the String() function.
|
||||||
|
// Sadly that function always includes a header, which we must strip out.
|
||||||
|
// TODO(tlim): Request the dns project add a function that returns
|
||||||
|
// the string without the header.
|
||||||
|
rr := r.ToRR()
|
||||||
|
header := rr.Header().String()
|
||||||
|
full := rr.String()
|
||||||
|
if !strings.HasPrefix(full, header) {
|
||||||
|
panic("dns.Hdr.String() not acting as we expect")
|
||||||
|
}
|
||||||
|
return full[len(header):]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeToTarget combines "extra" fields into .Target, and zeros the merged fields.
|
||||||
|
func (r *RecordConfig) MergeToTarget() {
|
||||||
|
// Merge "extra" fields into the Target.
|
||||||
|
r.Target = r.Content()
|
||||||
|
|
||||||
|
// Zap any fields that may have been merged.
|
||||||
|
r.MxPreference = 0
|
||||||
|
r.SrvPriority = 0
|
||||||
|
r.SrvWeight = 0
|
||||||
|
r.SrvPort = 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert RecordConfig -> dns.RR.
|
/// Convert RecordConfig -> dns.RR.
|
||||||
func (r *RecordConfig) ToRR() dns.RR {
|
func (rc *RecordConfig) ToRR() dns.RR {
|
||||||
|
|
||||||
// Note: The label is a FQDN ending in a ".". It will not put "@" in the Name field.
|
// Don't call this on fake types.
|
||||||
|
rdtype, ok := dns.StringToType[rc.Type]
|
||||||
// NB(tlim): An alternative way to do this would be
|
|
||||||
// to create the rr via: rr := TypeToRR[x]()
|
|
||||||
// then set the parameters. A benchmark may find that
|
|
||||||
// faster. This was faster to implement.
|
|
||||||
|
|
||||||
rdtype, ok := dns.StringToType[r.Type]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("No such DNS type as (%#v)\n", r.Type)
|
log.Fatalf("No such DNS type as (%#v)\n", rc.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr := dns.RR_Header{
|
// Magicallly create an RR of the correct type.
|
||||||
Name: r.NameFQDN + ".",
|
rr := dns.TypeToRR[rdtype]()
|
||||||
Rrtype: rdtype,
|
|
||||||
Class: dns.ClassINET,
|
// Fill in the header.
|
||||||
Ttl: r.TTL,
|
rr.Header().Name = rc.NameFQDN + "."
|
||||||
|
rr.Header().Rrtype = rdtype
|
||||||
|
rr.Header().Class = dns.ClassINET
|
||||||
|
rr.Header().Ttl = rc.TTL
|
||||||
|
if rc.TTL == 0 {
|
||||||
|
rr.Header().Ttl = DefaultTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle some special cases:
|
// Fill in the data.
|
||||||
switch rdtype {
|
switch rdtype {
|
||||||
|
case dns.TypeA:
|
||||||
|
rr.(*dns.A).A = net.ParseIP(rc.Target)
|
||||||
|
case dns.TypeCNAME:
|
||||||
|
rr.(*dns.CNAME).Target = rc.Target
|
||||||
|
case dns.TypePTR:
|
||||||
|
rr.(*dns.PTR).Ptr = rc.Target
|
||||||
case dns.TypeMX:
|
case dns.TypeMX:
|
||||||
// Has a Priority field.
|
rr.(*dns.MX).Preference = rc.MxPreference
|
||||||
return &dns.MX{Hdr: hdr, Preference: r.Priority, Mx: r.Target}
|
rr.(*dns.MX).Mx = rc.Target
|
||||||
|
case dns.TypeNS:
|
||||||
|
rr.(*dns.NS).Ns = rc.Target
|
||||||
|
case dns.TypeSOA:
|
||||||
|
t := strings.Replace(rc.Target, `\ `, ` `, -1)
|
||||||
|
parts := strings.Fields(t)
|
||||||
|
rr.(*dns.SOA).Ns = parts[0]
|
||||||
|
rr.(*dns.SOA).Mbox = parts[1]
|
||||||
|
rr.(*dns.SOA).Serial = atou32(parts[2])
|
||||||
|
rr.(*dns.SOA).Refresh = atou32(parts[3])
|
||||||
|
rr.(*dns.SOA).Retry = atou32(parts[4])
|
||||||
|
rr.(*dns.SOA).Expire = atou32(parts[5])
|
||||||
|
rr.(*dns.SOA).Minttl = atou32(parts[6])
|
||||||
|
case dns.TypeSRV:
|
||||||
|
rr.(*dns.SRV).Priority = rc.SrvPriority
|
||||||
|
rr.(*dns.SRV).Weight = rc.SrvWeight
|
||||||
|
rr.(*dns.SRV).Port = rc.SrvPort
|
||||||
|
rr.(*dns.SRV).Target = rc.Target
|
||||||
case dns.TypeTXT:
|
case dns.TypeTXT:
|
||||||
// Assure no problems due to quoting/unquoting:
|
rr.(*dns.TXT).Txt = []string{rc.Target}
|
||||||
return &dns.TXT{Hdr: hdr, Txt: []string{r.Target}}
|
|
||||||
default:
|
default:
|
||||||
|
panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ttl string
|
return rr
|
||||||
if r.TTL == 0 {
|
}
|
||||||
ttl = strconv.FormatUint(uint64(DefaultTTL), 10)
|
|
||||||
} else {
|
|
||||||
ttl = strconv.FormatUint(uint64(r.TTL), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := fmt.Sprintf("%s %s IN %s %s", r.NameFQDN, ttl, r.Type, r.Target)
|
func atou32(s string) uint32 {
|
||||||
rc, err := dns.NewRR(s)
|
i64, err := strconv.ParseInt(s, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("NewRR rejected RecordConfig: %#v (t=%#v)\n%v\n", s, r.Target, err)
|
panic(fmt.Sprintf("atou32 failed (%v) (err=%v", s, err))
|
||||||
}
|
}
|
||||||
return rc
|
return uint32(i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nameserver struct {
|
type Nameserver struct {
|
||||||
@ -196,8 +257,8 @@ func (dc *DomainConfig) Punycode() error {
|
|||||||
func (dc *DomainConfig) CombineMXs() {
|
func (dc *DomainConfig) CombineMXs() {
|
||||||
for _, rec := range dc.Records {
|
for _, rec := range dc.Records {
|
||||||
if rec.Type == "MX" {
|
if rec.Type == "MX" {
|
||||||
rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target)
|
rec.Target = fmt.Sprintf("%d %s", rec.MxPreference, rec.Target)
|
||||||
rec.Priority = 0
|
rec.MxPreference = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,12 @@ func TestHasRecordTypeName(t *testing.T) {
|
|||||||
|
|
||||||
func TestRR(t *testing.T) {
|
func TestRR(t *testing.T) {
|
||||||
experiment := RecordConfig{
|
experiment := RecordConfig{
|
||||||
Type: "A",
|
Type: "A",
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Target: "1.2.3.4",
|
Target: "1.2.3.4",
|
||||||
TTL: 0,
|
TTL: 0,
|
||||||
NameFQDN: "foo.example.com",
|
NameFQDN: "foo.example.com",
|
||||||
Priority: 0,
|
MxPreference: 0,
|
||||||
}
|
}
|
||||||
expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4"
|
expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4"
|
||||||
found := experiment.ToRR().String()
|
found := experiment.ToRR().String()
|
||||||
|
@ -173,6 +173,15 @@ function PTR(name, target) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SRV(name,priority,weight,port,target, recordModifiers...)
|
||||||
|
function SRV(name, priority, weight, port, target) {
|
||||||
|
checkArgs([_.isString, _.isNumber, _.isNumber, _.isNumber, _.isString], arguments, "SRV expects (name, priority, weight, port, target)")
|
||||||
|
var mods = getModifiers(arguments,5)
|
||||||
|
return function(d) {
|
||||||
|
addRecordSRV(d, "SRV", name, priority, weight, port, target, mods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TXT(name,target, recordModifiers...)
|
// TXT(name,target, recordModifiers...)
|
||||||
function TXT(name, target) {
|
function TXT(name, target) {
|
||||||
var mods = getModifiers(arguments,2)
|
var mods = getModifiers(arguments,2)
|
||||||
@ -312,6 +321,33 @@ function addRecord(d,type,name,target,mods) {
|
|||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addRecordSRV(d,type,name,srvpriority,srvweight,srvport,target,mods) {
|
||||||
|
var rec = {type: type, name: name, srvpriority: srvpriority, srvweight: srvweight, srvport: srvport, target: target, ttl:d.defaultTTL, meta:{}};
|
||||||
|
// for each modifier, decide based on type:
|
||||||
|
// - Function: call is with the record as the argument
|
||||||
|
// - Object: merge it into the metadata
|
||||||
|
// FIXME(tlim): Factor this code out to its own function.
|
||||||
|
if (mods) {
|
||||||
|
for (var i = 0; i< mods.length; i++) {
|
||||||
|
var m = mods[i]
|
||||||
|
if (_.isFunction(m)) {
|
||||||
|
m(rec);
|
||||||
|
} else if (_.isObject(m)) {
|
||||||
|
//convert transforms to strings
|
||||||
|
if (m.transform && _.isArray(m.transform)){
|
||||||
|
m.transform = format_tt(m.transform)
|
||||||
|
}
|
||||||
|
_.extend(rec.meta,m);
|
||||||
|
_.extend(rec.meta,m);
|
||||||
|
} else {
|
||||||
|
console.log("WARNING: Modifier type unsupported:", typeof m, "(Skipping!)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.records.push(rec);
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
//ip conversion functions from http://stackoverflow.com/a/8105740/121660
|
//ip conversion functions from http://stackoverflow.com/a/8105740/121660
|
||||||
// via http://javascript.about.com/library/blipconvert.htm
|
// via http://javascript.about.com/library/blipconvert.htm
|
||||||
function IP(dot)
|
function IP(dot)
|
||||||
|
101
pkg/js/static.go
101
pkg/js/static.go
@ -190,57 +190,60 @@ var _escData = map[string]*_escFile{
|
|||||||
|
|
||||||
"/helpers.js": {
|
"/helpers.js": {
|
||||||
local: "pkg/js/helpers.js",
|
local: "pkg/js/helpers.js",
|
||||||
size: 9590,
|
size: 11076,
|
||||||
modtime: 0,
|
modtime: 0,
|
||||||
compressed: `
|
compressed: `
|
||||||
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
|
||||||
|
kDbVHw+Gt787GzSUesQQ/woAAP//mqS3P0QrAAA=
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
|
|||||||
"CNAME": true,
|
"CNAME": true,
|
||||||
"IMPORT_TRANSFORM": false,
|
"IMPORT_TRANSFORM": false,
|
||||||
"MX": true,
|
"MX": true,
|
||||||
|
"SRV": true,
|
||||||
"TXT": true,
|
"TXT": true,
|
||||||
"NS": true,
|
"NS": true,
|
||||||
"PTR": true,
|
"PTR": true,
|
||||||
@ -96,7 +97,7 @@ func checkLabel(label string, rType string, domain string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//underscores are warnings
|
//underscores are warnings
|
||||||
if strings.ContainsRune(label, '_') {
|
if rType != "SRV" && strings.ContainsRune(label, '_') {
|
||||||
//unless it is in our exclusion list
|
//unless it is in our exclusion list
|
||||||
ok := false
|
ok := false
|
||||||
for _, ex := range expectedUnderscores {
|
for _, ex := range expectedUnderscores {
|
||||||
@ -146,6 +147,8 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
|
|||||||
check(checkTarget(target))
|
check(checkTarget(target))
|
||||||
case "ALIAS":
|
case "ALIAS":
|
||||||
check(checkTarget(target))
|
check(checkTarget(target))
|
||||||
|
case "SRV":
|
||||||
|
check(checkTarget(target))
|
||||||
case "TXT", "IMPORT_TRANSFORM":
|
case "TXT", "IMPORT_TRANSFORM":
|
||||||
default:
|
default:
|
||||||
if rec.Metadata["orig_custom_type"] != "" {
|
if rec.Metadata["orig_custom_type"] != "" {
|
||||||
@ -203,7 +206,7 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra
|
|||||||
r := newRec()
|
r := newRec()
|
||||||
r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name)
|
r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name)
|
||||||
dstDomain.Records = append(dstDomain.Records, r)
|
dstDomain.Records = append(dstDomain.Records, r)
|
||||||
case "MX", "NS", "TXT":
|
case "MX", "NS", "SRV", "TXT":
|
||||||
// Not imported.
|
// Not imported.
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
|
@ -97,7 +97,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
|
|||||||
rc.Target = v.Target
|
rc.Target = v.Target
|
||||||
case *dns.MX:
|
case *dns.MX:
|
||||||
rc.Target = v.Mx
|
rc.Target = v.Mx
|
||||||
rc.Priority = v.Preference
|
rc.MxPreference = v.Preference
|
||||||
case *dns.NS:
|
case *dns.NS:
|
||||||
rc.Target = v.Ns
|
rc.Target = v.Ns
|
||||||
case *dns.PTR:
|
case *dns.PTR:
|
||||||
@ -114,10 +114,15 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
|
|||||||
}
|
}
|
||||||
rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v",
|
rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v",
|
||||||
v.Ns, v.Mbox, new_serial, v.Refresh, v.Retry, v.Expire, v.Minttl)
|
v.Ns, v.Mbox, new_serial, v.Refresh, v.Retry, v.Expire, v.Minttl)
|
||||||
|
case *dns.SRV:
|
||||||
|
rc.Target = v.Target
|
||||||
|
rc.SrvPort = v.Port
|
||||||
|
rc.SrvWeight = v.Weight
|
||||||
|
rc.SrvPriority = v.Priority
|
||||||
case *dns.TXT:
|
case *dns.TXT:
|
||||||
rc.Target = strings.Join(v.Txt, " ")
|
rc.Target = strings.Join(v.Txt, " ")
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unimplemented zone record type=%s (%v)\n", rc.Type, rr)
|
log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr)
|
||||||
}
|
}
|
||||||
return rc, old_serial
|
return rc, old_serial
|
||||||
}
|
}
|
||||||
@ -129,12 +134,11 @@ func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig {
|
|||||||
Name: "@",
|
Name: "@",
|
||||||
}
|
}
|
||||||
soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin)
|
soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin)
|
||||||
//TODO(cpeterson): are these sane defaults?
|
|
||||||
if len(info.Ns) == 0 {
|
if len(info.Ns) == 0 {
|
||||||
info.Ns = "DEFAULT_NOT_SET"
|
info.Ns = "DEFAULT_NOT_SET."
|
||||||
}
|
}
|
||||||
if len(info.Mbox) == 0 {
|
if len(info.Mbox) == 0 {
|
||||||
info.Mbox = "DEFAULT_NOT_SET"
|
info.Mbox = "DEFAULT_NOT_SET."
|
||||||
}
|
}
|
||||||
if info.Serial == 0 {
|
if info.Serial == 0 {
|
||||||
info.Serial = 1
|
info.Serial = 1
|
||||||
|
@ -39,18 +39,36 @@ func (z *zoneGenData) Less(i, j int) bool {
|
|||||||
if rrtypeA != rrtypeB {
|
if rrtypeA != rrtypeB {
|
||||||
return zoneRrtypeLess(rrtypeA, rrtypeB)
|
return zoneRrtypeLess(rrtypeA, rrtypeB)
|
||||||
}
|
}
|
||||||
if rrtypeA == dns.TypeA {
|
switch rrtypeA {
|
||||||
|
case dns.TypeNS, dns.TypeTXT:
|
||||||
|
// pass through.
|
||||||
|
case dns.TypeA:
|
||||||
ta2, tb2 := a.(*dns.A), b.(*dns.A)
|
ta2, tb2 := a.(*dns.A), b.(*dns.A)
|
||||||
ipa, ipb := ta2.A.To4(), tb2.A.To4()
|
ipa, ipb := ta2.A.To4(), tb2.A.To4()
|
||||||
if ipa == nil || ipb == nil {
|
if ipa == nil || ipb == nil {
|
||||||
log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2)
|
log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2)
|
||||||
}
|
}
|
||||||
return bytes.Compare(ipa, ipb) == -1
|
return bytes.Compare(ipa, ipb) == -1
|
||||||
}
|
case dns.TypeMX:
|
||||||
if rrtypeA == dns.TypeMX {
|
|
||||||
ta2, tb2 := a.(*dns.MX), b.(*dns.MX)
|
ta2, tb2 := a.(*dns.MX), b.(*dns.MX)
|
||||||
pa, pb := ta2.Preference, tb2.Preference
|
pa, pb := ta2.Preference, tb2.Preference
|
||||||
return pa < pb
|
return pa < pb
|
||||||
|
case dns.TypeSRV:
|
||||||
|
ta2, tb2 := a.(*dns.SRV), b.(*dns.SRV)
|
||||||
|
pa, pb := ta2.Port, tb2.Port
|
||||||
|
if pa != pb {
|
||||||
|
return pa < pb
|
||||||
|
}
|
||||||
|
pa, pb = ta2.Priority, tb2.Priority
|
||||||
|
if pa != pb {
|
||||||
|
return pa < pb
|
||||||
|
}
|
||||||
|
pa, pb = ta2.Weight, tb2.Weight
|
||||||
|
if pa != pb {
|
||||||
|
return pa < pb
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("zoneGenData Less: unimplemented rtype %v", dns.TypeToString[rrtypeA]))
|
||||||
}
|
}
|
||||||
return a.String() < b.String()
|
return a.String() < b.String()
|
||||||
}
|
}
|
||||||
@ -92,6 +110,7 @@ func WriteZoneFile(w io.Writer, records []dns.RR, origin string) error {
|
|||||||
// be easy to read and pleasant to the eye.
|
// be easy to read and pleasant to the eye.
|
||||||
// * Within a label, SOA and NS records are listed first.
|
// * Within a label, SOA and NS records are listed first.
|
||||||
// * MX records are sorted numericly by preference value.
|
// * MX records are sorted numericly by preference value.
|
||||||
|
// * SRV records are sorted numericly by port, then priority, then weight.
|
||||||
// * A records are sorted by IP address, not lexicographically.
|
// * A records are sorted by IP address, not lexicographically.
|
||||||
// * Repeated labels are removed.
|
// * Repeated labels are removed.
|
||||||
// * $TTL is used to eliminate clutter. The most common TTL value is used.
|
// * $TTL is used to eliminate clutter. The most common TTL value is used.
|
||||||
@ -148,7 +167,7 @@ func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error {
|
|||||||
|
|
||||||
// items[2]: class
|
// items[2]: class
|
||||||
if hdr.Class != dns.ClassINET {
|
if hdr.Class != dns.ClassINET {
|
||||||
log.Fatalf("Unimplemented class=%v", items[2])
|
log.Fatalf("generateZoneFileHelper: Unimplemented class=%v", items[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
// items[3]: type
|
// items[3]: type
|
||||||
|
@ -156,6 +156,31 @@ _domainkey IN TXT "vvvv"
|
|||||||
google._domainkey IN TXT "\"foo\""
|
google._domainkey IN TXT "\"foo\""
|
||||||
`
|
`
|
||||||
|
|
||||||
|
func TestWriteZoneFileSrv(t *testing.T) {
|
||||||
|
//exhibits explicit ttls and long name
|
||||||
|
r1, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`)
|
||||||
|
r2, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 20 5050 foo.com.`)
|
||||||
|
r3, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`)
|
||||||
|
r4, _ := dns.NewRR(`bosun.org. 300 IN SRV 20 10 5050 foo.com.`)
|
||||||
|
r5, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`)
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5}, "bosun.org")
|
||||||
|
if buf.String() != testdataZFSRV {
|
||||||
|
t.Log(buf.String())
|
||||||
|
t.Log(testdataZFSRV)
|
||||||
|
t.Fatalf("Zone file does not match.")
|
||||||
|
}
|
||||||
|
parseAndRegen(t, buf, testdataZFSRV)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testdataZFSRV = `$TTL 300
|
||||||
|
@ IN SRV 10 10 5050 foo.com.
|
||||||
|
IN SRV 10 10 5050 foo.com.
|
||||||
|
IN SRV 10 20 5050 foo.com.
|
||||||
|
IN SRV 20 10 5050 foo.com.
|
||||||
|
IN SRV 10 10 9999 foo.com.
|
||||||
|
`
|
||||||
|
|
||||||
func TestWriteZoneFileOrder(t *testing.T) {
|
func TestWriteZoneFileOrder(t *testing.T) {
|
||||||
var records []dns.RR
|
var records []dns.RR
|
||||||
for i, td := range []string{
|
for i, td := range []string{
|
||||||
|
@ -344,12 +344,12 @@ func (c *cfRecord) toRecord(domain string) *models.RecordConfig {
|
|||||||
c.Content = dnsutil.AddOrigin(c.Content+".", domain)
|
c.Content = dnsutil.AddOrigin(c.Content+".", domain)
|
||||||
}
|
}
|
||||||
return &models.RecordConfig{
|
return &models.RecordConfig{
|
||||||
NameFQDN: c.Name,
|
NameFQDN: c.Name,
|
||||||
Type: c.Type,
|
Type: c.Type,
|
||||||
Target: c.Content,
|
Target: c.Content,
|
||||||
Priority: c.Priority,
|
MxPreference: c.Priority,
|
||||||
TTL: c.TTL,
|
TTL: c.TTL,
|
||||||
Original: c,
|
Original: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*
|
|||||||
}
|
}
|
||||||
prio := ""
|
prio := ""
|
||||||
if rec.Type == "MX" {
|
if rec.Type == "MX" {
|
||||||
prio = fmt.Sprintf(" %d ", rec.Priority)
|
prio = fmt.Sprintf(" %d ", rec.MxPreference)
|
||||||
}
|
}
|
||||||
arr := []*models.Correction{{
|
arr := []*models.Correction{{
|
||||||
Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.Name, rec.Type, rec.TTL, prio, content),
|
Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.Name, rec.Type, rec.TTL, prio, content),
|
||||||
@ -142,7 +142,7 @@ func (c *CloudflareApi) createRec(rec *models.RecordConfig, domainID string) []*
|
|||||||
Type: rec.Type,
|
Type: rec.Type,
|
||||||
TTL: rec.TTL,
|
TTL: rec.TTL,
|
||||||
Content: content,
|
Content: content,
|
||||||
Priority: rec.Priority,
|
Priority: rec.MxPreference,
|
||||||
}
|
}
|
||||||
endpoint := fmt.Sprintf(recordsURL, domainID)
|
endpoint := fmt.Sprintf(recordsURL, domainID)
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
@ -181,7 +181,7 @@ func (c *CloudflareApi) modifyRecord(domainID, recID string, proxied bool, rec *
|
|||||||
Priority uint16 `json:"priority"`
|
Priority uint16 `json:"priority"`
|
||||||
TTL uint32 `json:"ttl"`
|
TTL uint32 `json:"ttl"`
|
||||||
}
|
}
|
||||||
r := record{recID, proxied, rec.Name, rec.Type, rec.Target, rec.Priority, rec.TTL}
|
r := record{recID, proxied, rec.Name, rec.Type, rec.Target, rec.MxPreference, rec.TTL}
|
||||||
endpoint := fmt.Sprintf(singleRecordURL, domainID, recID)
|
endpoint := fmt.Sprintf(singleRecordURL, domainID, recID)
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
encoder := json.NewEncoder(buf)
|
encoder := json.NewEncoder(buf)
|
||||||
|
@ -33,11 +33,7 @@ type differ struct {
|
|||||||
|
|
||||||
// get normalized content for record. target, ttl, mxprio, and specified metadata
|
// get normalized content for record. target, ttl, mxprio, and specified metadata
|
||||||
func (d *differ) content(r *models.RecordConfig) string {
|
func (d *differ) content(r *models.RecordConfig) string {
|
||||||
content := fmt.Sprintf("%s %d", r.Target, r.TTL)
|
content := fmt.Sprintf("%v ttl=%d", r.Content(), r.TTL)
|
||||||
if r.Type == "MX" {
|
|
||||||
content += fmt.Sprintf(" priority=%d", r.Priority)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range d.extraValues {
|
for _, f := range d.extraValues {
|
||||||
for k, v := range f(r) {
|
for k, v := range f(r) {
|
||||||
content += fmt.Sprintf(" %s=%s", k, v)
|
content += fmt.Sprintf(" %s=%s", k, v)
|
||||||
|
@ -94,8 +94,8 @@ func TestMxPrio(t *testing.T) {
|
|||||||
desired := []*models.RecordConfig{
|
desired := []*models.RecordConfig{
|
||||||
myRecord("www MX 1 1.1.1.1"),
|
myRecord("www MX 1 1.1.1.1"),
|
||||||
}
|
}
|
||||||
existing[0].Priority = 10
|
existing[0].MxPreference = 10
|
||||||
desired[0].Priority = 20
|
desired[0].MxPreference = 20
|
||||||
checkLengths(t, existing, desired, 0, 0, 0, 1)
|
checkLengths(t, existing, desired, 0, 0, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +54,12 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
|
|||||||
r.Content += "."
|
r.Content += "."
|
||||||
}
|
}
|
||||||
rec := &models.RecordConfig{
|
rec := &models.RecordConfig{
|
||||||
NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name),
|
NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name),
|
||||||
Type: r.Type,
|
Type: r.Type,
|
||||||
Target: r.Content,
|
Target: r.Content,
|
||||||
TTL: uint32(r.TTL),
|
TTL: uint32(r.TTL),
|
||||||
Priority: uint16(r.Priority),
|
MxPreference: uint16(r.Priority),
|
||||||
Original: r,
|
Original: r,
|
||||||
}
|
}
|
||||||
actual = append(actual, rec)
|
actual = append(actual, rec)
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
|
|||||||
Type: rc.Type,
|
Type: rc.Type,
|
||||||
Content: rc.Target,
|
Content: rc.Target,
|
||||||
TTL: int(rc.TTL),
|
TTL: int(rc.TTL),
|
||||||
Priority: int(rc.Priority),
|
Priority: int(rc.MxPreference),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.Zones.CreateRecord(accountId, domainName, record)
|
_, err = client.Zones.CreateRecord(accountId, domainName, record)
|
||||||
@ -277,7 +277,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
|
|||||||
Type: rc.Type,
|
Type: rc.Type,
|
||||||
Content: rc.Target,
|
Content: rc.Target,
|
||||||
TTL: int(rc.TTL),
|
TTL: int(rc.TTL),
|
||||||
Priority: int(rc.Priority),
|
Priority: int(rc.MxPreference),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record)
|
_, err = client.Zones.UpdateRecord(accountId, domainName, old.ID, record)
|
||||||
|
@ -146,5 +146,5 @@ func newGandi(m map[string]string, metadata json.RawMessage) (providers.DNSServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("GANDI", newGandi)
|
providers.RegisterDomainServiceProviderType("GANDI", newGandi, providers.CanUsePTR)
|
||||||
}
|
}
|
||||||
|
@ -126,13 +126,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, want := range dc.Records {
|
for _, want := range dc.Records {
|
||||||
if want.Type == "MX" {
|
want.MergeToTarget()
|
||||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
|
||||||
want.Priority = 0
|
|
||||||
} else if want.Type == "TXT" {
|
|
||||||
//add quotes to txts
|
|
||||||
want.Target = fmt.Sprintf(`"%s"`, want.Target)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// first collect keys that have changed
|
// first collect keys that have changed
|
||||||
|
@ -41,7 +41,8 @@ func newProvider(conf map[string]string) (*nameDotCom, error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterRegistrarType("NAMEDOTCOM", newReg)
|
providers.RegisterRegistrarType("NAMEDOTCOM", newReg)
|
||||||
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, providers.CanUseAlias)
|
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, providers.CanUseSRV)
|
||||||
|
// PTR records are not supported https://www.name.com/support/articles/205188508-Reverse-DNS-records (2017-05-08)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -3,6 +3,7 @@ package namedotcom
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/dns/dnsutil"
|
"github.com/miekg/dns/dnsutil"
|
||||||
|
|
||||||
@ -97,14 +98,31 @@ func checkNSModifications(dc *models.DomainConfig) {
|
|||||||
func (r *nameComRecord) toRecord() *models.RecordConfig {
|
func (r *nameComRecord) toRecord() *models.RecordConfig {
|
||||||
ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
|
ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
|
||||||
prio, _ := strconv.ParseUint(r.Priority, 10, 16)
|
prio, _ := strconv.ParseUint(r.Priority, 10, 16)
|
||||||
return &models.RecordConfig{
|
rc := &models.RecordConfig{
|
||||||
NameFQDN: r.Name,
|
NameFQDN: r.Name,
|
||||||
Type: r.Type,
|
Type: r.Type,
|
||||||
Target: r.Content,
|
Target: r.Content,
|
||||||
TTL: uint32(ttl),
|
TTL: uint32(ttl),
|
||||||
Priority: uint16(prio),
|
|
||||||
Original: r,
|
Original: r,
|
||||||
}
|
}
|
||||||
|
switch r.Type {
|
||||||
|
case "A", "AAAA", "CNAME", "NS", "TXT":
|
||||||
|
// nothing additional.
|
||||||
|
case "MX":
|
||||||
|
rc.MxPreference = uint16(prio)
|
||||||
|
case "SRV":
|
||||||
|
parts := strings.Split(r.Content, " ")
|
||||||
|
weight, _ := strconv.ParseInt(parts[0], 10, 32)
|
||||||
|
port, _ := strconv.ParseInt(parts[1], 10, 32)
|
||||||
|
rc.SrvWeight = uint16(weight)
|
||||||
|
rc.SrvPort = uint16(port)
|
||||||
|
rc.SrvPriority = uint16(prio)
|
||||||
|
rc.MxPreference = 0
|
||||||
|
rc.Target = parts[2] + "."
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("toRecord unimplemented rtype %v", r.Type))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
type listRecordsResponse struct {
|
type listRecordsResponse struct {
|
||||||
@ -150,11 +168,20 @@ func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error
|
|||||||
Type: rc.Type,
|
Type: rc.Type,
|
||||||
Content: target,
|
Content: target,
|
||||||
TTL: rc.TTL,
|
TTL: rc.TTL,
|
||||||
Priority: rc.Priority,
|
Priority: rc.MxPreference,
|
||||||
}
|
}
|
||||||
if dat.Hostname == "@" {
|
if dat.Hostname == "@" {
|
||||||
dat.Hostname = ""
|
dat.Hostname = ""
|
||||||
}
|
}
|
||||||
|
switch rc.Type {
|
||||||
|
case "A", "AAAA", "CNAME", "MX", "NS", "TXT":
|
||||||
|
// nothing
|
||||||
|
case "SRV":
|
||||||
|
dat.Content = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.Target)
|
||||||
|
dat.Priority = rc.SrvPriority
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type))
|
||||||
|
}
|
||||||
resp, err := n.post(n.apiCreateRecord(domain), dat)
|
resp, err := n.post(n.apiCreateRecord(domain), dat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -44,6 +44,8 @@ const (
|
|||||||
CanUseAlias Capability = 1 << iota
|
CanUseAlias Capability = 1 << iota
|
||||||
// CanUsePTR indicates the provider can handle PTR records
|
// CanUsePTR indicates the provider can handle PTR records
|
||||||
CanUsePTR
|
CanUsePTR
|
||||||
|
// CanUseSRV indicates the provider can handle SRV records
|
||||||
|
CanUseSRV
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProviderHasCabability(pType string, cap Capability) bool {
|
func ProviderHasCabability(pType string, cap Capability) bool {
|
||||||
|
@ -42,7 +42,7 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (providers.DNSSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR)
|
providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53, providers.CanUsePTR, providers.CanUseSRV)
|
||||||
}
|
}
|
||||||
func sPtr(s string) *string {
|
func sPtr(s string) *string {
|
||||||
return &s
|
return &s
|
||||||
@ -142,12 +142,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, want := range dc.Records {
|
for _, want := range dc.Records {
|
||||||
if want.Type == "MX" {
|
want.MergeToTarget()
|
||||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
|
||||||
want.Priority = 0
|
|
||||||
} else if want.Type == "TXT" {
|
|
||||||
want.Target = fmt.Sprintf(`"%s"`, want.Target) //FIXME: better escaping/quoting
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//diff
|
//diff
|
||||||
|
Reference in New Issue
Block a user