mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
BIND Provider: Pick default TTL automatically for zonefiles (#109)
* BIND Provider: Pick default TTL automatically for zonefiles
This commit is contained in:
@@ -70,7 +70,7 @@ func pretty(zonename string, filename string, r io.Reader, defaultTTL uint32) {
|
|||||||
l = append(l, x.RR)
|
l = append(l, x.RR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bind.WriteZoneFile(os.Stdout, l, zonename, defaultTTL)
|
bind.WriteZoneFile(os.Stdout, l, zonename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rrFormat outputs the zonefile in either DSL or TSV format.
|
// rrFormat outputs the zonefile in either DSL or TSV format.
|
||||||
|
@@ -238,7 +238,7 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
|
|||||||
for _, r := range dc.Records {
|
for _, r := range dc.Records {
|
||||||
zonefilerecords = append(zonefilerecords, r.RR())
|
zonefilerecords = append(zonefilerecords, r.RR())
|
||||||
}
|
}
|
||||||
err = WriteZoneFile(zf, zonefilerecords, dc.Name, models.DefaultTTL)
|
err = WriteZoneFile(zf, zonefilerecords, dc.Name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("WriteZoneFile error: %v\n", err)
|
log.Fatalf("WriteZoneFile error: %v\n", err)
|
||||||
|
@@ -57,8 +57,38 @@ func (z *zoneGenData) Less(i, j int) bool {
|
|||||||
return a.String() < b.String()
|
return a.String() < b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mostCommonTtl returns the most common TTL in a set of records. If there is
|
||||||
|
// a tie, the highest TTL is selected. This makes the results consistent.
|
||||||
|
// NS records are not included in the analysis because Tom said so.
|
||||||
|
func mostCommonTtl(records []dns.RR) uint32 {
|
||||||
|
// Index the TTLs in use:
|
||||||
|
d := make(map[uint32]int)
|
||||||
|
for _, r := range records {
|
||||||
|
if r.Header().Rrtype != dns.TypeNS {
|
||||||
|
d[r.Header().Ttl]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find the largest count:
|
||||||
|
var mc int
|
||||||
|
for _, value := range d {
|
||||||
|
if value > mc {
|
||||||
|
mc = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find the largest key with that count:
|
||||||
|
var mk uint32
|
||||||
|
for key, value := range d {
|
||||||
|
if value == mc {
|
||||||
|
if key > mk {
|
||||||
|
mk = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mk
|
||||||
|
}
|
||||||
|
|
||||||
// WriteZoneFile writes a beautifully formatted zone file.
|
// WriteZoneFile writes a beautifully formatted zone file.
|
||||||
func WriteZoneFile(w io.Writer, records []dns.RR, origin string, defaultTtl uint32) error {
|
func WriteZoneFile(w io.Writer, records []dns.RR, origin string) error {
|
||||||
// This function prioritizes beauty over efficiency.
|
// This function prioritizes beauty over efficiency.
|
||||||
// * The zone records are sorted by label, grouped by subzones to
|
// * The zone records are sorted by label, grouped by subzones to
|
||||||
// be easy to read and pleasant to the eye.
|
// be easy to read and pleasant to the eye.
|
||||||
@@ -66,9 +96,11 @@ func WriteZoneFile(w io.Writer, records []dns.RR, origin string, defaultTtl uint
|
|||||||
// * MX records are sorted numericly by preference value.
|
// * MX records are sorted numericly by preference value.
|
||||||
// * 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.
|
// * $TTL is used to eliminate clutter. The most common TTL value is used.
|
||||||
// * "@" is used instead of the apex domain name.
|
// * "@" is used instead of the apex domain name.
|
||||||
|
|
||||||
|
defaultTtl := mostCommonTtl(records)
|
||||||
|
|
||||||
z := &zoneGenData{
|
z := &zoneGenData{
|
||||||
Origin: origin,
|
Origin: origin,
|
||||||
DefaultTtl: defaultTtl,
|
DefaultTtl: defaultTtl,
|
||||||
|
@@ -27,7 +27,7 @@ func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) {
|
|||||||
}
|
}
|
||||||
// Generate it back:
|
// Generate it back:
|
||||||
buf2 := &bytes.Buffer{}
|
buf2 := &bytes.Buffer{}
|
||||||
WriteZoneFile(buf2, parsed, "bosun.org.", 300)
|
WriteZoneFile(buf2, parsed, "bosun.org.")
|
||||||
|
|
||||||
// Compare:
|
// Compare:
|
||||||
if buf2.String() != expected {
|
if buf2.String() != expected {
|
||||||
@@ -35,6 +35,49 @@ func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMostCommonTtl(t *testing.T) {
|
||||||
|
var records []dns.RR
|
||||||
|
var g, e uint32
|
||||||
|
r1, _ := dns.NewRR("bosun.org. 100 IN A 1.1.1.1")
|
||||||
|
r2, _ := dns.NewRR("bosun.org. 200 IN A 1.1.1.1")
|
||||||
|
r3, _ := dns.NewRR("bosun.org. 300 IN A 1.1.1.1")
|
||||||
|
r4, _ := dns.NewRR("bosun.org. 400 IN NS foo.bosun.org.")
|
||||||
|
r5, _ := dns.NewRR("bosun.org. 400 IN NS bar.bosun.org.")
|
||||||
|
|
||||||
|
// All records are TTL=100
|
||||||
|
records = nil
|
||||||
|
records, e = append(records, r1, r1, r1), 100
|
||||||
|
g = mostCommonTtl(records)
|
||||||
|
if e != g {
|
||||||
|
t.Fatalf("expected %d; got %d\n", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixture of TTLs with an obvious winner.
|
||||||
|
records = nil
|
||||||
|
records, e = append(records, r1, r2, r2), 200
|
||||||
|
g = mostCommonTtl(records)
|
||||||
|
if e != g {
|
||||||
|
t.Fatalf("expected %d; got %d\n", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3-way tie. Largest TTL should be used.
|
||||||
|
records = nil
|
||||||
|
records, e = append(records, r1, r2, r3), 300
|
||||||
|
g = mostCommonTtl(records)
|
||||||
|
if e != g {
|
||||||
|
t.Fatalf("expected %d; got %d\n", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NS records are ignored.
|
||||||
|
records = nil
|
||||||
|
records, e = append(records, r1, r4, r5), 100
|
||||||
|
g = mostCommonTtl(records)
|
||||||
|
if e != g {
|
||||||
|
t.Fatalf("expected %d; got %d\n", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// func WriteZoneFile
|
// func WriteZoneFile
|
||||||
|
|
||||||
func TestWriteZoneFileSimple(t *testing.T) {
|
func TestWriteZoneFileSimple(t *testing.T) {
|
||||||
@@ -42,7 +85,7 @@ func TestWriteZoneFileSimple(t *testing.T) {
|
|||||||
r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154")
|
r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154")
|
||||||
r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.")
|
r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.")
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org.", 300)
|
WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org.")
|
||||||
expected := `$TTL 300
|
expected := `$TTL 300
|
||||||
@ IN A 192.30.252.153
|
@ IN A 192.30.252.153
|
||||||
IN A 192.30.252.154
|
IN A 192.30.252.154
|
||||||
@@ -57,6 +100,28 @@ www IN CNAME bosun.org.
|
|||||||
parseAndRegen(t, buf, expected)
|
parseAndRegen(t, buf, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteZoneFileSimpleTtl(t *testing.T) {
|
||||||
|
r1, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.153")
|
||||||
|
r2, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.154")
|
||||||
|
r3, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.155")
|
||||||
|
r4, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.")
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4}, "bosun.org.")
|
||||||
|
expected := `$TTL 100
|
||||||
|
@ IN A 192.30.252.153
|
||||||
|
IN A 192.30.252.154
|
||||||
|
IN A 192.30.252.155
|
||||||
|
www 300 IN CNAME bosun.org.
|
||||||
|
`
|
||||||
|
if buf.String() != expected {
|
||||||
|
t.Log(buf.String())
|
||||||
|
t.Log(expected)
|
||||||
|
t.Fatalf("Zone file does not match.")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndRegen(t, buf, expected)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriteZoneFileMx(t *testing.T) {
|
func TestWriteZoneFileMx(t *testing.T) {
|
||||||
//exhibits explicit ttls and long name
|
//exhibits explicit ttls and long name
|
||||||
r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`)
|
r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`)
|
||||||
@@ -70,7 +135,7 @@ func TestWriteZoneFileMx(t *testing.T) {
|
|||||||
r8, _ := dns.NewRR(`_domainkey.bosun.org. 300 IN TXT "vvvv"`)
|
r8, _ := dns.NewRR(`_domainkey.bosun.org. 300 IN TXT "vvvv"`)
|
||||||
r9, _ := dns.NewRR(`google._domainkey.bosun.org. 300 IN TXT "\"foo\""`)
|
r9, _ := dns.NewRR(`google._domainkey.bosun.org. 300 IN TXT "\"foo\""`)
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6, r7, r8, r9}, "bosun.org", 300)
|
WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6, r7, r8, r9}, "bosun.org")
|
||||||
if buf.String() != testdataZFMX {
|
if buf.String() != testdataZFMX {
|
||||||
t.Log(buf.String())
|
t.Log(buf.String())
|
||||||
t.Log(testdataZFMX)
|
t.Log(testdataZFMX)
|
||||||
@@ -117,7 +182,7 @@ func TestWriteZoneFileOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
WriteZoneFile(buf, records, "stackoverflow.com.", 300)
|
WriteZoneFile(buf, records, "stackoverflow.com.")
|
||||||
// Compare
|
// Compare
|
||||||
if buf.String() != testdataOrder {
|
if buf.String() != testdataOrder {
|
||||||
t.Log("Found:")
|
t.Log("Found:")
|
||||||
@@ -138,7 +203,7 @@ func TestWriteZoneFileOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Generate
|
// Generate
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
WriteZoneFile(buf, records, "stackoverflow.com.", 300)
|
WriteZoneFile(buf, records, "stackoverflow.com.")
|
||||||
// Compare
|
// Compare
|
||||||
if buf.String() != testdataOrder {
|
if buf.String() != testdataOrder {
|
||||||
t.Log(buf.String())
|
t.Log(buf.String())
|
||||||
|
Reference in New Issue
Block a user