1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Refactor RecordConfig: Add getters/setters (#314)

* Replace RecordConfig.Name and .NameFQDN with getters and setters.
* Replace RecordConfig.Target with getters and setters.
* Eliminate the CombinedTarget concept.
* Add RecordConfig.PopulateFromString to reduce code in all providers.
* encode and decode name.com txt records (#315)
* Replace fmt.Errorf with errors.Errorf
This commit is contained in:
Tom Limoncelli
2018-02-15 12:02:50 -05:00
committed by GitHub
parent 324b1ea930
commit de4455942b
37 changed files with 1237 additions and 965 deletions

View File

@@ -11,6 +11,7 @@ import (
const defaultAPIBase = "api.name.com"
// NameCom describes a connection to the NDC API.
type NameCom struct {
APIUrl string `json:"apiurl"`
APIUser string `json:"apiuser"`

View File

@@ -12,6 +12,7 @@ import (
var nsRegex = regexp.MustCompile(`ns([1-4])[a-z]{3}\.name\.com`)
// GetNameservers gets the nameservers set on a domain.
func (n *NameCom) GetNameservers(domain string) ([]*models.Nameserver, error) {
// This is an interesting edge case. Name.com expects you to SET the nameservers to ns[1-4].name.com,
// but it will internally set it to ns1xyz.name.com, where xyz is a uniqueish 3 letters.
@@ -44,6 +45,7 @@ func (n *NameCom) getNameserversRaw(domain string) ([]string, error) {
return response.Nameservers, nil
}
// GetRegistrarCorrections gathers corrections that would being n to match dc.
func (n *NameCom) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
nss, err := n.getNameserversRaw(dc.Name)
if err != nil {

View File

@@ -3,10 +3,10 @@ package namedotcom
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/namedotcom/go/namecom"
"github.com/pkg/errors"
"github.com/miekg/dns/dnsutil"
@@ -21,6 +21,7 @@ var defaultNameservers = []*models.Nameserver{
{Name: "ns4.name.com"},
}
// GetDomainCorrections gathers correctios that would bring n to match dc.
func (n *NameCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode()
records, err := n.getRecords(dc.Name)
@@ -29,7 +30,7 @@ func (n *NameCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corre
}
actual := make([]*models.RecordConfig, len(records))
for i, r := range records {
actual[i] = toRecord(r)
actual[i] = toRecord(r, dc.Name)
}
for _, rec := range dc.Records {
@@ -83,43 +84,32 @@ func checkNSModifications(dc *models.DomainConfig) {
dc.Records = newList
}
// finds a string surrounded by quotes that might contain an escaped quote charactor.
var quotedStringRegexp = regexp.MustCompile("\"((?:[^\"\\\\]|\\\\.)*)\"")
func toRecord(r *namecom.Record) *models.RecordConfig {
func toRecord(r *namecom.Record, origin string) *models.RecordConfig {
rc := &models.RecordConfig{
NameFQDN: strings.TrimSuffix(r.Fqdn, "."),
Type: r.Type,
Target: r.Answer,
TTL: r.TTL,
Original: r,
}
switch r.Type { // #rtype_variations
case "A", "AAAA", "ANAME", "CNAME", "NS":
// nothing additional.
if !strings.HasSuffix(r.Fqdn, ".") {
panic(errors.Errorf("namedotcom suddenly changed protocol. Bailing. (%v)", r.Fqdn))
}
fqdn := r.Fqdn[:len(r.Fqdn)-1]
rc.SetLabelFromFQDN(fqdn, origin)
switch rtype := r.Type; rtype { // #rtype_variations
case "TXT":
if r.Answer[0] == '"' && r.Answer[len(r.Answer)-1] == '"' {
txtStrings := []string{}
for _, t := range quotedStringRegexp.FindAllStringSubmatch(r.Answer, -1) {
txtStrings = append(txtStrings, t[1])
}
rc.SetTxts(txtStrings)
}
rc.SetTargetTXTs(decodeTxt(r.Answer))
case "MX":
rc.MxPreference = uint16(r.Priority)
if err := rc.SetTargetMX(uint16(r.Priority), r.Answer); err != nil {
panic(errors.Wrap(err, "unparsable MX record received from ndc"))
}
case "SRV":
parts := strings.Split(r.Answer, " ")
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(r.Priority)
rc.MxPreference = 0
rc.Target = parts[2] + "."
default:
panic(fmt.Sprintf("toRecord unimplemented rtype %v", r.Type))
// We panic so that we quickly find any switch statements
// that have not been updated for a new RR type.
if err := rc.SetTargetSRVPriorityString(uint16(r.Priority), r.Answer+"."); err != nil {
panic(errors.Wrap(err, "unparsable SRV record received from ndc"))
}
default: // "A", "AAAA", "ANAME", "CNAME", "NS"
if err := rc.PopulateFromString(rtype, r.Answer, r.Fqdn); err != nil {
panic(errors.Wrap(err, "unparsable record received from ndc"))
}
}
return rc
}
@@ -163,17 +153,11 @@ func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error {
TTL: rc.TTL,
Priority: uint32(rc.MxPreference),
}
switch rc.Type { // #rtype_variations
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS":
// nothing
case "TXT":
if len(rc.TxtStrings) > 1 {
record.Answer = ""
for _, t := range rc.TxtStrings {
record.Answer += "\"" + strings.Replace(t, "\"", "\\\"", -1) + "\""
}
}
record.Answer = encodeTxt(rc.TxtStrings)
case "SRV":
record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.Target)
record.Priority = uint32(rc.SrvPriority)
@@ -186,6 +170,37 @@ func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error {
return err
}
// makeTxt encodes TxtStrings for sending in the CREATE/MODIFY API:
func encodeTxt(txts []string) string {
ans := txts[0]
if len(txts) > 1 {
ans = ""
for _, t := range txts {
ans += `"` + strings.Replace(t, `"`, `\"`, -1) + `"`
}
}
return ans
}
// finds a string surrounded by quotes that might contain an escaped quote charactor.
var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
// decodeTxt decodes the TXT record as received from name.com and
// returns the list of strings.
func decodeTxt(s string) []string {
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
txtStrings := []string{}
for _, t := range quotedStringRegexp.FindAllStringSubmatch(s, -1) {
txtString := strings.Replace(t[1], `\"`, `"`, -1)
txtStrings = append(txtStrings, txtString)
}
return txtStrings
}
return []string{s}
}
func (n *NameCom) deleteRecord(id int32, domain string) error {
request := &namecom.DeleteRecordRequest{
DomainName: domain,

View File

@@ -0,0 +1,51 @@
package namedotcom
import (
"strings"
"testing"
)
var txtData = []struct {
decoded []string
encoded string
}{
{[]string{`simple`}, `simple`},
{[]string{`changed`}, `changed`},
{[]string{`with spaces`}, `with spaces`},
{[]string{`with whitespace`}, `with whitespace`},
{[]string{"one", "two"}, `"one""two"`},
{[]string{"eh", "bee", "cee"}, `"eh""bee""cee"`},
{[]string{"o\"ne", "tw\"o"}, `"o\"ne""tw\"o"`},
{[]string{"dimple"}, `dimple`},
{[]string{"fun", "two"}, `"fun""two"`},
{[]string{"eh", "bzz", "cee"}, `"eh""bzz""cee"`},
}
func TestEncodeTxt(t *testing.T) {
// Test encoded the lists of strings into a string:
for i, test := range txtData {
enc := encodeTxt(test.decoded)
if enc != test.encoded {
t.Errorf("%v: txt\n data: []string{%v}\nexpected: %s\n got: %s",
i, "`"+strings.Join(test.decoded, "`, `")+"`", test.encoded, enc)
}
}
}
func TestDecodeTxt(t *testing.T) {
// Test decoded a string into the list of strings:
for i, test := range txtData {
data := test.encoded
got := decodeTxt(data)
wanted := test.decoded
if len(got) != len(wanted) {
t.Errorf("%v: txt\n decode: %v\nexpected: `%v`\n got: `%v`\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
} else {
for j := range got {
if got[j] != wanted[j] {
t.Errorf("%v: txt\n decode: %v\nexpected: `%v`\n got: `%v`\n", i, data, strings.Join(wanted, "`, `"), strings.Join(got, "`, `"))
}
}
}
}
}