dnscontrol/pkg/prettyzone/prettyzone.go
Tom Limoncelli 87ad01d194
Add "get-zone" command (#613)
* Add GetZoneRecords to DNSProvider interface
* dnscontrol now uses ufave/cli/v2
* NEW: get-zones.md
* HasRecordTypeName should be a method on models.Records not models.DomainConfig
* Implement BIND's GetZoneRecords
* new WriteZoneFile implemented
* go mod vendor
* Update docs to use get-zone instead of convertzone
* Add CanGetZone capability and update all providers.
* Get all zones for a provider at once (#626)
* implement GetZoneRecords for cloudflare
* munge cloudflare ttls
* Implement GetZoneRecords for cloudflare (#625)

Co-authored-by: Craig Peterson <192540+captncraig@users.noreply.github.com>
2020-02-18 08:59:18 -05:00

142 lines
3.4 KiB
Go

package prettyzone
// Generate zonefiles.
// This generates a zonefile that prioritizes beauty over efficiency.
import (
"fmt"
"io"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/v2/models"
"github.com/miekg/dns"
)
// 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 models.Records) uint32 {
// Index the TTLs in use:
d := make(map[uint32]int)
for _, r := range records {
if r.Type != "NS" {
d[r.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
}
// WriteZoneFileRR is a helper for when you have []dns.RR instead of models.Records
func WriteZoneFileRR(w io.Writer, records []dns.RR, origin string, serial uint32) error {
return WriteZoneFileRC(w, models.RRstoRCs(records, origin, serial), origin)
}
// WriteZoneFileRC writes a beautifully formatted zone file.
func WriteZoneFileRC(w io.Writer, records models.Records, origin string) error {
// This function prioritizes beauty over output size.
// * The zone records are sorted by label, grouped by subzones to
// be easy to read and pleasant to the eye.
// * Within a label, SOA and NS records are listed first.
// * 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.
// * Repeated labels are removed.
// * $TTL is used to eliminate clutter. The most common TTL value is used.
// * "@" is used instead of the apex domain name.
z := PrettySort(records, origin, mostCommonTTL(records))
return z.generateZoneFileHelper(w)
}
func PrettySort(records models.Records, origin string, defaultTTL uint32) *zoneGenData {
if defaultTTL == 0 {
defaultTTL = mostCommonTTL(records)
}
z := &zoneGenData{
Origin: origin + ".",
DefaultTTL: defaultTTL,
}
z.Records = nil
for _, r := range records {
z.Records = append(z.Records, r)
}
return z
}
// generateZoneFileHelper creates a pretty zonefile.
func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error {
nameShortPrevious := ""
sort.Sort(z)
fmt.Fprintln(w, "$TTL", z.DefaultTTL)
for i, rr := range z.Records {
// Fake types are commented out.
prefix := ""
_, ok := dns.StringToType[rr.Type]
if !ok {
prefix = ";"
}
// name
nameShort := rr.Name
name := nameShort
if (prefix == "") && (i > 0 && nameShort == nameShortPrevious) {
name = ""
} else {
name = nameShort
}
nameShortPrevious = nameShort
// ttl
ttl := ""
if rr.TTL != z.DefaultTTL && rr.TTL != 0 {
ttl = fmt.Sprint(rr.TTL)
}
// type
typeStr := rr.Type
// the remaining line
target := rr.GetTargetCombined()
fmt.Fprintf(w, "%s%s\n",
prefix, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}))
}
return nil
}
func formatLine(lengths []int, fields []string) string {
c := 0
result := ""
for i, length := range lengths {
item := fields[i]
for len(result) < c {
result += " "
}
if item != "" {
result += item + " "
}
c += length + 1
}
return strings.TrimRight(result, " ")
}