Add support for TXT records with multiple strings (BIND, ROUTE53) (#293)

* BIND: Support TXT records with multiple strings (#289)
* ROUTE53: Add support for TXT records with multiple strings (#292)
This commit is contained in:
Tom Limoncelli 2018-01-04 19:19:35 -05:00 committed by GitHub
parent d051f51a59
commit de88bfe8b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 489 additions and 184 deletions

View file

@ -9,10 +9,14 @@ parameters:
TXT adds an TXT record To a domain. The name should be the relative TXT adds an TXT record To a domain. The name should be the relative
label for the record. Use `@` for the domain apex. label for the record. Use `@` for the domain apex.
The contents is a single string. While DNS permits multiple The contents is either a single or multiple strings. To
strings in TXT records, that is not supported at this time. specify multiple strings, include them in an array.
The string is a JavaScript string (quoted using single or double TXT records with multiple strings are only supported by some
providers. DNSControl will produce a validation error if the
provider does not support multiple strings.
Each string is a JavaScript string (quoted using single or double
quotes). The (somewhat complex) quoting rules of the DNS protocol quotes). The (somewhat complex) quoting rules of the DNS protocol
will be done for you. will be done for you.
@ -24,6 +28,9 @@ Modifers can be any number of [record modifiers](#record-modifiers) or json obje
D("example.com", REGISTRAR, ...., D("example.com", REGISTRAR, ....,
TXT('@', '598611146-3338560'), TXT('@', '598611146-3338560'),
TXT('listserve', 'google-site-verification=12345'), TXT('listserve', 'google-site-verification=12345'),
TXT('multiple', ['one', 'two', 'three']), // Multiple strings
TXT('quoted', 'any "quotes" and escapes? ugh; no worries!'),
TXT('_domainkey', 't=y; o=-;') // Escapes are done for you automatically.
); );
{%endhighlight%} {%endhighlight%}

View file

@ -112,7 +112,7 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
} }
dom.Records = append(dom.Records, &rc) dom.Records = append(dom.Records, &rc)
} }
models.Downcase(dom.Records) models.PostProcessRecords(dom.Records)
dom2, _ := dom.Copy() dom2, _ := dom.Copy()
// get corrections for first time // get corrections for first time
corrections, err := prv.GetDomainCorrections(dom) corrections, err := prv.GetDomainCorrections(dom)
@ -237,7 +237,17 @@ func srv(name string, priority, weight, port uint16, target string) *rec {
} }
func txt(name, target string) *rec { func txt(name, target string) *rec {
return makeRec(name, target, "TXT") // FYI: This must match the algorithm in pkg/js/helpers.js TXT.
r := makeRec(name, target, "TXT")
r.TxtStrings = []string{target}
return r
}
func txtmulti(name string, target []string) *rec {
// FYI: This must match the algorithm in pkg/js/helpers.js TXT.
r := makeRec(name, target[0], "TXT")
r.TxtStrings = target
return r
} }
func caa(name string, tag string, flag uint8, target string) *rec { func caa(name string, tag string, flag uint8, target string) *rec {
@ -427,15 +437,42 @@ func makeTests(t *testing.T) []*TestCase {
) )
} }
// Case // TXT (single)
tests = append(tests, tc("Empty"), tests = append(tests, tc("Empty"),
// TXT
tc("Empty"), tc("Empty"),
tc("Create a TXT", txt("foo", "simple")), tc("Create a TXT", txt("foo", "simple")),
tc("Change a TXT", txt("foo", "changed")), tc("Change a TXT", txt("foo", "changed")),
tc("Empty"),
tc("Create a TXT with spaces", txt("foo", "with spaces")), tc("Create a TXT with spaces", txt("foo", "with spaces")),
tc("Change a TXT with spaces", txt("foo", "with whitespace")), tc("Change a TXT with spaces", txt("foo", "with whitespace")),
tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})),
) )
// TXTMulti
if !providers.ProviderHasCabability(*providerToRun, providers.CanUseTXTMulti) {
t.Log("Skipping TXTMulti Tests because provider does not support them")
} else {
tests = append(tests,
tc("Empty"),
tc("Create TXTMulti 1",
txtmulti("foo1", []string{"simple"}),
),
tc("Create TXTMulti 2",
txtmulti("foo1", []string{"simple"}),
txtmulti("foo2", []string{"one", "two"}),
),
tc("Create TXTMulti 3",
txtmulti("foo1", []string{"simple"}),
txtmulti("foo2", []string{"one", "two"}),
txtmulti("foo3", []string{"eh", "bee", "cee"}),
),
tc("Change TXTMulti",
txtmulti("foo1", []string{"dimple"}),
txtmulti("foo2", []string{"fun", "two"}),
txtmulti("foo3", []string{"eh", "bzz", "cee"}),
),
)
}
return tests return tests
} }

View file

@ -1,4 +1,4 @@
$TTL 300 $TTL 300
@ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2017091830 3600 600 604800 1440 @ IN SOA DEFAULT_NOT_SET. DEFAULT_NOT_SET. 2018010281 3600 600 604800 1440
IN NS ns1.otherdomain.tld. IN NS ns1.otherdomain.tld.
IN NS ns2.otherdomain.tld. IN NS ns2.otherdomain.tld.

View file

@ -101,6 +101,7 @@ type RecordConfig struct {
TlsaUsage uint8 `json:"tlsausage,omitempty"` TlsaUsage uint8 `json:"tlsausage,omitempty"`
TlsaSelector uint8 `json:"tlsaselector,omitempty"` TlsaSelector uint8 `json:"tlsaselector,omitempty"`
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"` TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
CombinedTarget bool `json:"-"` CombinedTarget bool `json:"-"`
@ -247,7 +248,7 @@ func (rc *RecordConfig) ToRR() dns.RR {
rr.(*dns.TLSA).Selector = rc.TlsaSelector rr.(*dns.TLSA).Selector = rc.TlsaSelector
rr.(*dns.TLSA).Certificate = rc.Target rr.(*dns.TLSA).Certificate = rc.Target
case dns.TypeTXT: case dns.TypeTXT:
rr.(*dns.TXT).Txt = []string{rc.Target} rr.(*dns.TXT).Txt = rc.TxtStrings
default: default:
panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type)) panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type))
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements
@ -275,6 +276,12 @@ func (r Records) Grouped() map[RecordKey]Records {
return groups return groups
} }
// PostProcessRecords does any post-processing of the downloaded DNS records.
func PostProcessRecords(recs []*RecordConfig) {
Downcase(recs)
fixTxt(recs)
}
// Downcase converts all labels and targets to lowercase in a list of RecordConfig. // Downcase converts all labels and targets to lowercase in a list of RecordConfig.
func Downcase(recs []*RecordConfig) { func Downcase(recs []*RecordConfig) {
for _, r := range recs { for _, r := range recs {
@ -292,6 +299,17 @@ func Downcase(recs []*RecordConfig) {
return return
} }
// fixTxt fixes TXT records generated by providers that do not understand CanUseTXTMulti.
func fixTxt(recs []*RecordConfig) {
for _, r := range recs {
if r.Type == "TXT" {
if len(r.TxtStrings) == 0 {
r.TxtStrings = []string{r.Target}
}
}
}
}
type RecordKey struct { type RecordKey struct {
Name string Name string
Type string Type string
@ -453,7 +471,6 @@ func (dc *DomainConfig) CombineCAAs() {
panic(pm) panic(pm)
} }
rec.Target = rec.Content() rec.Target = rec.Content()
fmt.Printf("DEBUG: NEW TARGET: %v\n", rec.Target)
rec.CombinedTarget = true rec.CombinedTarget = true
} }
} }

57
models/txt.go Normal file
View file

@ -0,0 +1,57 @@
package models
import "strings"
// SetTxt sets the value of a TXT record to s.
func (rc *RecordConfig) SetTxt(s string) {
rc.Target = s
rc.TxtStrings = []string{s}
}
// SetTxts sets the value of a TXT record to the list of strings s.
func (rc *RecordConfig) SetTxts(s []string) {
rc.Target = s[0]
rc.TxtStrings = s
}
// SetTxtParse sets the value of TXT record if the list of strings is combined into one string.
// `foo` -> []string{"foo"}
// `"foo"` -> []string{"foo"}
// `"foo" "bar"` -> []string{"foo" "bar"}
func (rc *RecordConfig) SetTxtParse(s string) {
rc.SetTxts(ParseQuotedTxt(s))
}
// IsQuoted returns true if the string starts and ends with a double quote.
func IsQuoted(s string) bool {
if s == "" {
return false
}
if len(s) < 2 {
return false
}
if s[0] == '"' && s[len(s)-1] == s[0] {
return true
}
return false
}
// StripQuotes returns the string with the starting and ending quotes removed.
func StripQuotes(s string) string {
if IsQuoted(s) {
return s[1 : len(s)-1]
}
return s
}
// ParseQuotedTxt returns the individual strings of a combined quoted string.
// `foo` -> []string{"foo"}
// `"foo"` -> []string{"foo"}
// `"foo" "bar"` -> []string{"foo" "bar"}
// NOTE: it is assumed there is exactly one space between the quotes.
func ParseQuotedTxt(s string) []string {
if !IsQuoted(s) {
return []string{s}
}
return strings.Split(StripQuotes(s), `" "`)
}

71
models/txt_test.go Normal file
View file

@ -0,0 +1,71 @@
package models
import (
"testing"
)
func TestIsQuoted(t *testing.T) {
tests := []struct {
d1 string
e1 bool
}{
{``, false},
{`foo`, false},
{`""`, true},
{`"a"`, true},
{`"bb"`, true},
{`"ccc"`, true},
{`"aaa" "bbb"`, true},
}
for i, test := range tests {
r := IsQuoted(test.d1)
if r != test.e1 {
t.Errorf("%v: expected (%v) got (%v)", i, test.e1, r)
}
}
}
func TestStripQuotes(t *testing.T) {
tests := []struct {
d1 string
e1 string
}{
{``, ``},
{`a`, `a`},
{`bb`, `bb`},
{`ccc`, `ccc`},
{`dddd`, `dddd`},
{`"A"`, `A`},
{`"BB"`, `BB`},
{`"CCC"`, `CCC`},
{`"DDDD"`, `DDDD`},
{`"EEEEE"`, `EEEEE`},
{`"aaa" "bbb"`, `aaa" "bbb`},
}
for i, test := range tests {
r := StripQuotes(test.d1)
if r != test.e1 {
t.Errorf("%v: expected (%v) got (%v)", i, test.e1, r)
}
}
}
func TestSetTxtParse(t *testing.T) {
tests := []struct {
d1 string
e1 string
e2 []string
}{
{``, ``, []string{``}},
{`foo`, `foo`, []string{`foo`}},
{`"foo"`, `foo`, []string{`foo`}},
{`"aaa" "bbb"`, `aaa`, []string{`aaa`, `bbb`}},
}
for i, test := range tests {
x := &RecordConfig{Type: "TXT"}
x.SetTxtParse(test.d1)
if x.Target != test.e1 {
t.Errorf("%v: expected Target=(%v) got (%v)", i, test.e1, x.Target)
}
}
}

View file

@ -220,8 +220,32 @@ var TLSA = recordBuilder('TLSA', {
}, },
}); });
function isStringOrArray(x) {
return _.isString(x) || _.isArray(x);
}
// TXT(name,target, recordModifiers...) // TXT(name,target, recordModifiers...)
var TXT = recordBuilder('TXT'); var TXT = recordBuilder("TXT", {
args: [["name", _.isString], ["target", isStringOrArray]],
transform: function(record, args, modifiers) {
record.name = args.name;
// Store the strings twice:
// .target is the first string
// .txtstrings is the individual strings.
// NOTE: If there are more than 1 string, providers should only access
// .txtstrings, thus it doesn't matter what we store in .target.
// However, by storing the first string there, it improves backwards
// compatibility when the len(array) == 1 and (intentionally) breaks
// broken providers early in the integration tests.
if (_.isString(args.target)) {
record.target = args.target;
record.txtstrings = [args.target];
} else {
record.target = args.target[0]
record.txtstrings = args.target;
}
}
});
// MX(name,priority,target, recordModifiers...) // MX(name,priority,target, recordModifiers...)
var MX = recordBuilder('MX', { var MX = recordBuilder('MX', {

View file

@ -11,7 +11,8 @@
"type": "TXT", "type": "TXT",
"name": "_dmarc", "name": "_dmarc",
"target": "v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1", "target": "v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1",
"ttl": 300 "ttl": 300,
"txtstrings":["v=DMARC1\\; p=reject\\; sp=reject\\; pct=100\\; rua=mailto:xx...@yyyy.com\\; ruf=mailto:xx...@yyyy.com\\; fo=1"]
} }
] ]
} }

View file

@ -0,0 +1,6 @@
D("foo.com","none"
, TXT("@","simple")
, TXT("@",["one"])
, TXT("@",["bonie", "clyde"])
, TXT("@",["straw", "wood", "brick"])
);

View file

@ -0,0 +1,37 @@
{
"registrars": [],
"dns_providers": [],
"domains": [
{
"name": "foo.com",
"registrar": "none",
"dnsProviders": {},
"records": [
{
"type": "TXT",
"name": "@",
"target": "simple",
"txtstrings": [ "simple" ]
},
{
"type": "TXT",
"name": "@",
"target": "one",
"txtstrings": [ "one" ]
},
{
"type": "TXT",
"name": "@",
"target": "bonie",
"txtstrings": [ "bonie", "clyde" ]
},
{
"type": "TXT",
"name": "@",
"target": "straw",
"txtstrings": [ "straw", "wood", "brick" ]
}
]
}
]
}

View file

@ -192,83 +192,88 @@ var _escData = map[string]*_escFile{
"/helpers.js": { "/helpers.js": {
local: "pkg/js/helpers.js", local: "pkg/js/helpers.js",
size: 16222, size: 17122,
modtime: 0, modtime: 0,
compressed: ` compressed: `
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
`, `,
}, },

View file

@ -35,7 +35,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
} }
if flatten, ok := txt.Metadata["flatten"]; ok && strings.HasPrefix(txt.Target, "v=spf1") { if flatten, ok := txt.Metadata["flatten"]; ok && strings.HasPrefix(txt.Target, "v=spf1") {
rec = rec.Flatten(flatten) rec = rec.Flatten(flatten)
txt.Target = rec.TXT() txt.SetTxt(rec.TXT())
} }
// now split if needed // now split if needed
if split, ok := txt.Metadata["split"]; ok { if split, ok := txt.Metadata["split"]; ok {
@ -46,10 +46,10 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
recs := rec.TXTSplit(split + "." + domain.Name) recs := rec.TXTSplit(split + "." + domain.Name)
for k, v := range recs { for k, v := range recs {
if k == "@" { if k == "@" {
txt.Target = v txt.SetTxt(v)
} else { } else {
cp, _ := txt.Copy() cp, _ := txt.Copy()
cp.Target = v cp.SetTxt(v)
cp.NameFQDN = k cp.NameFQDN = k
cp.Name = dnsutil.TrimDomainName(k, domain.Name) cp.Name = dnsutil.TrimDomainName(k, domain.Name)
domain.Records = append(domain.Records, cp) domain.Records = append(domain.Records, cp)

View file

@ -252,6 +252,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
for _, domain := range config.Domains { for _, domain := range config.Domains {
pTypes := []string{} pTypes := []string{}
txtMultiDissenters := []string{}
for p := range domain.DNSProviders { for p := range domain.DNSProviders {
pType, ok := ptypeMap[p] pType, ok := ptypeMap[p]
if !ok { if !ok {
@ -264,6 +265,11 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
if domain.KeepUnknown && providers.ProviderHasCabability(pType, providers.CantUseNOPURGE) { if domain.KeepUnknown && providers.ProviderHasCabability(pType, providers.CantUseNOPURGE) {
errs = append(errs, fmt.Errorf("%s uses NO_PURGE which is not supported by %s(%s)", domain.Name, p, pType)) errs = append(errs, fmt.Errorf("%s uses NO_PURGE which is not supported by %s(%s)", domain.Name, p, pType))
} }
// Record if any providers do not support TXTMulti:
if !providers.ProviderHasCabability(pType, providers.CanUseTXTMulti) {
txtMultiDissenters = append(txtMultiDissenters, p)
}
} }
// Normalize Nameservers. // Normalize Nameservers.
@ -272,7 +278,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
ns.Name = strings.TrimRight(ns.Name, ".") ns.Name = strings.TrimRight(ns.Name, ".")
} }
// Normalize Records. // Normalize Records.
models.Downcase(domain.Records) models.PostProcessRecords(domain.Records)
for _, rec := range domain.Records { for _, rec := range domain.Records {
if rec.TTL == 0 { if rec.TTL == 0 {
rec.TTL = models.DefaultTTL rec.TTL = models.DefaultTTL
@ -315,6 +321,12 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
errs = append(errs, fmt.Errorf("TLSA MatchingType %d is invalid in record %s (domain %s)", errs = append(errs, fmt.Errorf("TLSA MatchingType %d is invalid in record %s (domain %s)",
rec.TlsaMatchingType, rec.Name, domain.Name)) rec.TlsaMatchingType, rec.Name, domain.Name))
} }
} else if rec.Type == "TXT" && len(txtMultiDissenters) != 0 && len(rec.TxtStrings) > 1 {
// There are providers that don't support TXTMulti yet there is
// a TXT record with multiple strings:
errs = append(errs,
fmt.Errorf("TXT records with multiple strings (label %v domain: %v) not supported by %s",
rec.Name, domain.Name, strings.Join(txtMultiDissenters, ",")))
} }
// Populate FQDN: // Populate FQDN:

View file

@ -16,20 +16,20 @@ type adProvider struct {
psLog string psLog string
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
providers.DocCreateDomains: providers.Cannot("AD depends on the zone already existing on the dns server"),
providers.DocOfficiallySupported: providers.Can(),
providers.CanUseAlias: providers.Cannot(), providers.CanUseAlias: providers.Cannot(),
providers.CanUseSRV: providers.Cannot(),
providers.CanUsePTR: providers.Cannot(),
providers.CanUseCAA: providers.Cannot(), providers.CanUseCAA: providers.Cannot(),
providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("AD depends on the zone already existing on the dns server"),
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
providers.DocOfficiallySupported: providers.Can(),
} }
// Register with the dnscontrol system. // Register with the dnscontrol system.
// This establishes the name (all caps), and the function to call to initialize it. // This establishes the name (all caps), and the function to call to initialize it.
func init() { func init() {
providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", newDNS, docNotes) providers.RegisterDomainServiceProviderType("ACTIVEDIRECTORY_PS", newDNS, features)
} }
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {

View file

@ -47,7 +47,7 @@ func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
} }
// Normalize // Normalize
models.Downcase(foundRecords) models.PostProcessRecords(foundRecords)
differ := diff.New(dc) differ := diff.New(dc)
_, creates, dels, modifications := differ.IncrementalDiff(foundRecords) _, creates, dels, modifications := differ.IncrementalDiff(foundRecords)

View file

@ -30,9 +30,15 @@ import (
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
) )
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Can(), providers.CanUseCAA: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Can(),
providers.CanUseTXTMulti: providers.Can(),
providers.CantUseNOPURGE: providers.Cannot(),
providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."), providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Can(), providers.DocOfficiallySupported: providers.Can(),
} }
@ -56,13 +62,7 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("BIND", initBind, providers.RegisterDomainServiceProviderType("BIND", initBind, features)
providers.CanUsePTR,
providers.CanUseSRV,
providers.CanUseCAA,
providers.CanUseTLSA,
providers.CantUseNOPURGE,
docNotes)
} }
type SoaInfo struct { type SoaInfo struct {
@ -144,6 +144,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
rc.Target = v.Certificate rc.Target = v.Certificate
case *dns.TXT: case *dns.TXT:
rc.Target = strings.Join(v.Txt, " ") rc.Target = strings.Join(v.Txt, " ")
rc.TxtStrings = v.Txt
default: default:
log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr) log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr)
} }
@ -243,7 +244,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
} }
// Normalize // Normalize
models.Downcase(foundRecords) models.PostProcessRecords(foundRecords)
differ := diff.New(dc) differ := diff.New(dc)
_, create, del, mod := differ.IncrementalDiff(foundRecords) _, create, del, mod := differ.IncrementalDiff(foundRecords)

View file

@ -19,6 +19,9 @@ const (
// CanUseSRV indicates the provider can handle SRV records // CanUseSRV indicates the provider can handle SRV records
CanUseSRV CanUseSRV
// CanUseTXTMulti indicates the provider can handle TXT records with multiple strings
CanUseTXTMulti
// CanUseCAA indicates the provider can handle CAA records // CanUseCAA indicates the provider can handle CAA records
CanUseCAA CanUseCAA
@ -66,12 +69,12 @@ type ProviderMetadata interface{}
var Notes = map[string]DocumentationNotes{} var Notes = map[string]DocumentationNotes{}
func unwrapProviderCapabilities(pName string, meta []ProviderMetadata) { func unwrapProviderCapabilities(pName string, meta []ProviderMetadata) {
for _, pm := range meta {
switch x := pm.(type) {
case Capability:
if providerCapabilities[pName] == nil { if providerCapabilities[pName] == nil {
providerCapabilities[pName] = map[Capability]bool{} providerCapabilities[pName] = map[Capability]bool{}
} }
for _, pm := range meta {
switch x := pm.(type) {
case Capability:
providerCapabilities[pName][x] = true providerCapabilities[pName][x] = true
case DocumentationNotes: case DocumentationNotes:
if Notes[pName] == nil { if Notes[pName] == nil {
@ -79,6 +82,7 @@ func unwrapProviderCapabilities(pName string, meta []ProviderMetadata) {
} }
for k, v := range x { for k, v := range x {
Notes[pName][k] = v Notes[pName][k] = v
providerCapabilities[pName][k] = v.HasFeature
} }
default: default:
log.Fatalf("Unrecognized ProviderMetadata type: %T", pm) log.Fatalf("Unrecognized ProviderMetadata type: %T", pm)

View file

@ -33,15 +33,17 @@ Domain level metadata available:
- ip_conversions - ip_conversions
*/ */
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"),
providers.DocCreateDomains: providers.Can(),
providers.DocOfficiallySupported: providers.Can(),
providers.CanUseAlias: providers.Can("CF automatically flattens CNAME records into A records dynamically"), providers.CanUseAlias: providers.Can("CF automatically flattens CNAME records into A records dynamically"),
providers.CanUseCAA: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Cannot("Cloudflare will not work well in situations where it is not the only DNS server"),
providers.DocOfficiallySupported: providers.Can(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseSRV, providers.CanUseAlias, providers.CanUseCAA, docNotes) providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, features)
providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "") providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "")
providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "") providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "")
} }
@ -122,7 +124,7 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
checkNSModifications(dc) checkNSModifications(dc)
// Normalize // Normalize
models.Downcase(records) models.PostProcessRecords(records)
differ := diff.New(dc, getProxyMetadata) differ := diff.New(dc, getProxyMetadata)
_, create, del, mod := differ.IncrementalDiff(records) _, create, del, mod := differ.IncrementalDiff(records)

View file

@ -60,13 +60,14 @@ func NewDo(m map[string]string, metadata json.RawMessage) (providers.DNSServiceP
return api, nil return api, nil
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocCreateDomains: providers.Can(), providers.DocCreateDomains: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(), providers.DocOfficiallySupported: providers.Cannot(),
providers.CanUseSRV: providers.Can(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, providers.CanUseSRV, docNotes) providers.RegisterDomainServiceProviderType("DIGITALOCEAN", NewDo, features)
} }
func (api *DoApi) EnsureDomainExists(domain string) error { func (api *DoApi) EnsureDomainExists(domain string) error {
@ -102,7 +103,7 @@ func (api *DoApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corre
} }
// Normalize // Normalize
models.Downcase(existingRecords) models.PostProcessRecords(existingRecords)
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(existingRecords) _, create, delete, modify := differ.IncrementalDiff(existingRecords)

View file

@ -15,16 +15,20 @@ import (
dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple" dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple"
) )
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"), providers.CanUseAlias: providers.Can(),
providers.DocCreateDomains: providers.Cannot(), providers.CanUseCAA: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(), providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Cannot(), providers.CanUseTLSA: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot(),
providers.DocDualHost: providers.Cannot("DNSimple does not allow sufficient control over the apex NS records"),
providers.DocOfficiallySupported: providers.Cannot(),
} }
func init() { func init() {
providers.RegisterRegistrarType("DNSIMPLE", newReg) providers.RegisterRegistrarType("DNSIMPLE", newReg)
providers.RegisterDomainServiceProviderType("DNSIMPLE", newDsp, providers.CanUsePTR, providers.CanUseAlias, providers.CanUseCAA, providers.CanUseSRV, docNotes) providers.RegisterDomainServiceProviderType("DNSIMPLE", newDsp, features)
} }
const stateRegistered = "registered" const stateRegistered = "registered"
@ -92,7 +96,7 @@ func (c *DnsimpleApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
}) })
// Normalize // Normalize
models.Downcase(actual) models.PostProcessRecords(actual)
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(actual) _, create, delete, modify := differ.IncrementalDiff(actual)

View file

@ -26,19 +26,17 @@ Info required in `creds.json`:
*/ */
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.CanUseCAA: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CantUseNOPURGE: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"), providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"),
providers.DocOfficiallySupported: providers.Cannot(), providers.DocOfficiallySupported: providers.Cannot(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("GANDI", newDsp, providers.RegisterDomainServiceProviderType("GANDI", newDsp, features)
providers.CanUseCAA,
providers.CanUsePTR,
providers.CanUseSRV,
providers.CantUseNOPURGE,
docNotes,
)
providers.RegisterRegistrarType("GANDI", newReg) providers.RegisterRegistrarType("GANDI", newReg)
} }
@ -121,7 +119,7 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
dc.Records = recordsToKeep dc.Records = recordsToKeep
// Normalize // Normalize
models.Downcase(foundRecords) models.PostProcessRecords(foundRecords)
differ := diff.New(dc) differ := diff.New(dc)
_, create, del, mod := differ.IncrementalDiff(foundRecords) _, create, del, mod := differ.IncrementalDiff(foundRecords)

View file

@ -192,8 +192,10 @@ func convert(r *gandirecord.RecordInfo, origin string) *models.RecordConfig {
TTL: uint32(r.Ttl), TTL: uint32(r.Ttl),
} }
switch r.Type { switch r.Type {
case "A", "AAAA", "NS", "CNAME", "PTR", "TXT": case "A", "AAAA", "NS", "CNAME", "PTR":
// no-op // no-op
case "TXT":
rc.SetTxtParse(r.Value)
case "CAA": case "CAA":
var err error var err error
rc.CaaTag, rc.CaaFlag, rc.Target, err = models.SplitCombinedCaaValue(r.Value) rc.CaaTag, rc.CaaFlag, rc.Target, err = models.SplitCombinedCaaValue(r.Value)

View file

@ -14,14 +14,17 @@ import (
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
) )
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Can(),
providers.DocCreateDomains: providers.Can(), providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Can(), providers.DocOfficiallySupported: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseCAA: providers.Can(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("GCLOUD", New, providers.CanUsePTR, providers.CanUseSRV, providers.CanUseCAA, docNotes) providers.RegisterDomainServiceProviderType("GCLOUD", New, features)
} }
type gcloud struct { type gcloud struct {
@ -137,7 +140,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
} }
// Normalize // Normalize
models.Downcase(existingRecords) models.PostProcessRecords(existingRecords)
// first collect keys that have changed // first collect keys that have changed
differ := diff.New(dc) differ := diff.New(dc)

View file

@ -13,9 +13,10 @@ import (
"net/url" "net/url"
"golang.org/x/oauth2"
"regexp" "regexp"
"strings" "strings"
"golang.org/x/oauth2"
) )
/* /*
@ -84,14 +85,14 @@ func NewLinode(m map[string]string, metadata json.RawMessage) (providers.DNSServ
return api, nil return api, nil
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocOfficiallySupported: providers.Cannot(),
providers.DocDualHost: providers.Cannot(), providers.DocDualHost: providers.Cannot(),
providers.DocOfficiallySupported: providers.Cannot(),
} }
func init() { func init() {
// SRV support is in this provider, but Linode doesn't seem to support it properly // SRV support is in this provider, but Linode doesn't seem to support it properly
providers.RegisterDomainServiceProviderType("LINODE", NewLinode, docNotes) providers.RegisterDomainServiceProviderType("LINODE", NewLinode, features)
} }
func (api *LinodeApi) GetNameservers(domain string) ([]*models.Nameserver, error) { func (api *LinodeApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
@ -138,7 +139,7 @@ func (api *LinodeApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
} }
// Normalize // Normalize
models.Downcase(existingRecords) models.PostProcessRecords(existingRecords)
// Linode doesn't allow selecting an arbitrary TTL, only a set of predefined values // Linode doesn't allow selecting an arbitrary TTL, only a set of predefined values
// We need to make sure we don't change it every time if it is as close as it's going to get // We need to make sure we don't change it every time if it is as close as it's going to get

View file

@ -25,20 +25,21 @@ type Namecheap struct {
client *nc.Client client *nc.Client
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocCreateDomains: providers.Cannot("Requires domain registered through their service"),
providers.DocOfficiallySupported: providers.Cannot(),
providers.DocDualHost: providers.Cannot("Doesn't allow control of apex NS records"),
providers.CanUseAlias: providers.Cannot(), providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Cannot(), providers.CanUseCAA: providers.Cannot(),
providers.CanUseSRV: providers.Cannot("The namecheap web console allows you to make SRV records, but their api does not let you read or set them"),
providers.CanUsePTR: providers.Cannot(), providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Cannot("The namecheap web console allows you to make SRV records, but their api does not let you read or set them"),
providers.CanUseTLSA: providers.Cannot(), providers.CanUseTLSA: providers.Cannot(),
providers.CantUseNOPURGE: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("Requires domain registered through their service"),
providers.DocDualHost: providers.Cannot("Doesn't allow control of apex NS records"),
providers.DocOfficiallySupported: providers.Cannot(),
} }
func init() { func init() {
providers.RegisterRegistrarType("NAMECHEAP", newReg) providers.RegisterRegistrarType("NAMECHEAP", newReg)
providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, providers.CantUseNOPURGE, docNotes) providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, features)
providers.RegisterCustomRecordType("URL", "NAMECHEAP", "") providers.RegisterCustomRecordType("URL", "NAMECHEAP", "")
providers.RegisterCustomRecordType("URL301", "NAMECHEAP", "") providers.RegisterCustomRecordType("URL301", "NAMECHEAP", "")
providers.RegisterCustomRecordType("FRAME", "NAMECHEAP", "") providers.RegisterCustomRecordType("FRAME", "NAMECHEAP", "")
@ -156,7 +157,7 @@ func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor
} }
// Normalize // Normalize
models.Downcase(actual) models.PostProcessRecords(actual)
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(actual) _, create, delete, modify := differ.IncrementalDiff(actual)

View file

@ -19,11 +19,13 @@ type nameDotCom struct {
APIKey string `json:"apikey"` APIKey string `json:"apikey"`
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Cannot("Apex NS records not editable"), providers.CanUseAlias: providers.Can(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocOfficiallySupported: providers.Can(),
providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"), providers.CanUsePTR: providers.Cannot("PTR records are not supported (See Link)", "https://www.name.com/support/articles/205188508-Reverse-DNS-records"),
providers.CanUseSRV: providers.Can(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocDualHost: providers.Cannot("Apex NS records not editable"),
providers.DocOfficiallySupported: providers.Can(),
} }
func newReg(conf map[string]string) (providers.Registrar, error) { func newReg(conf map[string]string) (providers.Registrar, error) {
@ -48,7 +50,7 @@ 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.CanUseSRV, docNotes) providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp, features)
} }
/// ///

View file

@ -38,7 +38,7 @@ func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
checkNSModifications(dc) checkNSModifications(dc)
// Normalize // Normalize
models.Downcase(actual) models.PostProcessRecords(actual)
differ := diff.New(dc) differ := diff.New(dc)
_, create, del, mod := differ.IncrementalDiff(actual) _, create, del, mod := differ.IncrementalDiff(actual)

View file

@ -67,7 +67,7 @@ func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correct
desiredGrouped := dc.Records.Grouped() desiredGrouped := dc.Records.Grouped()
// Normalize // Normalize
models.Downcase(found) models.PostProcessRecords(found)
differ := diff.New(dc) differ := diff.New(dc)
changedGroups := differ.ChangedGroups(found) changedGroups := differ.ChangedGroups(found)

View file

@ -3,13 +3,14 @@ package ovh
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
"github.com/miekg/dns/dnsutil" "github.com/miekg/dns/dnsutil"
"github.com/xlucas/go-ovh/ovh" "github.com/xlucas/go-ovh/ovh"
"sort"
"strings"
) )
type ovhProvider struct { type ovhProvider struct {
@ -17,14 +18,15 @@ type ovhProvider struct {
zones map[string]bool zones map[string]bool
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Can(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocOfficiallySupported: providers.Cannot(),
providers.CanUseAlias: providers.Cannot(), providers.CanUseAlias: providers.Cannot(),
providers.CanUseTLSA: providers.Can(),
providers.CanUseCAA: providers.Cannot(), providers.CanUseCAA: providers.Cannot(),
providers.CanUsePTR: providers.Cannot(), providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Cannot("New domains require registration"),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(),
} }
func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) { func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) {
@ -50,7 +52,7 @@ func newReg(conf map[string]string) (providers.Registrar, error) {
func init() { func init() {
providers.RegisterRegistrarType("OVH", newReg) providers.RegisterRegistrarType("OVH", newReg)
providers.RegisterDomainServiceProviderType("OVH", newDsp, providers.CanUseSRV, providers.CanUseTLSA, docNotes) providers.RegisterDomainServiceProviderType("OVH", newDsp, features)
} }
func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) { func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
@ -124,7 +126,7 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
} }
// Normalize // Normalize
models.Downcase(actual) models.PostProcessRecords(actual)
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(actual) _, create, delete, modify := differ.IncrementalDiff(actual)

View file

@ -55,15 +55,19 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider
return api, nil return api, nil
} }
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.DocDualHost: providers.Can(),
providers.DocCreateDomains: providers.Can(),
providers.DocOfficiallySupported: providers.Can(),
providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. They do have 'ALIAS' CNAME types to point at various AWS infrastructure, but dnscontrol has not implemented those."), providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. They do have 'ALIAS' CNAME types to point at various AWS infrastructure, but dnscontrol has not implemented those."),
providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Can(),
providers.DocOfficiallySupported: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTXTMulti: providers.Can(),
providers.CanUseCAA: providers.Can(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53Dsp, providers.CanUsePTR, providers.CanUseSRV, providers.CanUseCAA, docNotes) providers.RegisterDomainServiceProviderType("ROUTE53", newRoute53Dsp, features)
providers.RegisterRegistrarType("ROUTE53", newRoute53Reg) providers.RegisterRegistrarType("ROUTE53", newRoute53Reg)
} }
@ -170,7 +174,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
} }
// Normalize // Normalize
models.Downcase(existingRecords) models.PostProcessRecords(existingRecords)
//diff //diff
differ := diff.New(dc) differ := diff.New(dc)

View file

@ -21,8 +21,12 @@ type SoftLayer struct {
Session *session.Session Session *session.Session
} }
var features = providers.DocumentationNotes{
providers.CanUseSRV: providers.Can(),
}
func init() { func init() {
providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, providers.CanUseSRV) providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, features)
} }
func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) { func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
@ -164,7 +168,7 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.
} }
// Normalize // Normalize
models.Downcase(actual) models.PostProcessRecords(actual)
return actual, nil return actual, nil
} }

View file

@ -24,16 +24,18 @@ Info required in `creds.json`:
*/ */
var docNotes = providers.DocumentationNotes{ var features = providers.DocumentationNotes{
providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Can(),
providers.CanUsePTR: providers.Cannot(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTLSA: providers.Cannot(),
providers.DocCreateDomains: providers.Can(), providers.DocCreateDomains: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(), providers.DocOfficiallySupported: providers.Cannot(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseTLSA: providers.Cannot(),
providers.CanUsePTR: providers.Cannot(),
} }
func init() { func init() {
providers.RegisterDomainServiceProviderType("VULTR", NewVultr, providers.CanUseSRV, providers.CanUseCAA, docNotes) providers.RegisterDomainServiceProviderType("VULTR", NewVultr, features)
} }
// VultrApi represents the Vultr DNSServiceProvider // VultrApi represents the Vultr DNSServiceProvider
@ -98,7 +100,7 @@ func (api *VultrApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
} }
// Normalize // Normalize
models.Downcase(curRecords) models.PostProcessRecords(curRecords)
differ := diff.New(dc) differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(curRecords) _, create, delete, modify := differ.IncrementalDiff(curRecords)