mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
TTL warnings should be more verbose (#2069)
This commit is contained in:
@ -3,6 +3,7 @@ package normalize
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/miekg/dns/dnsutil"
|
"github.com/miekg/dns/dnsutil"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns false if target does not validate.
|
// Returns false if target does not validate.
|
||||||
@ -581,9 +583,10 @@ func checkDuplicates(records []*models.RecordConfig) (errs []error) {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func uniq(s []uint32) []uint32 {
|
// uniq returns the unique values in a map. The result is sorted lexigraphically.
|
||||||
seen := make(map[uint32]struct{})
|
func uniq(s []string) []string {
|
||||||
var result []uint32
|
seen := make(map[string]struct{})
|
||||||
|
var result []string
|
||||||
|
|
||||||
for _, k := range s {
|
for _, k := range s {
|
||||||
if _, ok := seen[k]; !ok {
|
if _, ok := seen[k]; !ok {
|
||||||
@ -591,33 +594,113 @@ func uniq(s []uint32) []uint32 {
|
|||||||
result = append(result, k)
|
result = append(result, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLabelHasMultipleTTLs(records []*models.RecordConfig) (errs []error) {
|
func checkLabelHasMultipleTTLs(records []*models.RecordConfig) (errs []error) {
|
||||||
m := make(map[string][]uint32)
|
// The RFCs say that all records at a particular label should have
|
||||||
for _, r := range records {
|
// the same TTL. Most providers don't care, and if they do the
|
||||||
label := fmt.Sprintf("%s %s", r.GetLabelFQDN(), r.Type)
|
// dnscontrol provider code usually picks the lowest TTL for all of them.
|
||||||
|
|
||||||
// collect the TTLs at this label.
|
// General algorithm:
|
||||||
m[label] = append(m[label], r.TTL)
|
// gather all records at a particular label.
|
||||||
|
// has[label] -> ttl -> type(s)
|
||||||
|
// for each label, if there is more than one ttl, output ttl:A/TXT ttl:TXT/NS
|
||||||
|
|
||||||
|
// Find the inconsistencies:
|
||||||
|
m := make(map[string]map[uint32]map[string]bool)
|
||||||
|
for _, r := range records {
|
||||||
|
label := r.GetLabelFQDN()
|
||||||
|
ttl := r.TTL
|
||||||
|
rtype := r.Type
|
||||||
|
|
||||||
|
if _, ok := m[label]; !ok {
|
||||||
|
m[label] = make(map[uint32]map[string]bool)
|
||||||
|
}
|
||||||
|
if _, ok := m[label][ttl]; !ok {
|
||||||
|
m[label][ttl] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
m[label][ttl][rtype] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for label := range m {
|
labels := make([]string, len(m))
|
||||||
// The RFCs say that all records at a particular label should have
|
i := 0
|
||||||
// the same TTL. Most providers don't care, and if they do the
|
for k := range m {
|
||||||
// code usually picks the lowest TTL for all of them.
|
labels[i] = k
|
||||||
//
|
i++
|
||||||
// If after the uniq() pass we still have more than one ttl, it
|
}
|
||||||
// means we have multiple TTLs for that label.
|
|
||||||
u := uniq(m[label])
|
sort.Strings(labels)
|
||||||
if len(u) > 1 {
|
slices.Compact(labels)
|
||||||
errs = append(errs, Warning{fmt.Errorf("label with multipe TTLs: %s (%v)", label, u)})
|
|
||||||
|
// Less clear error message:
|
||||||
|
// for _, label := range labels {
|
||||||
|
// if len(m[label]) > 1 {
|
||||||
|
// result := ""
|
||||||
|
// for ttl, v := range m[label] {
|
||||||
|
// result += fmt.Sprintf(" %d:", ttl)
|
||||||
|
|
||||||
|
// rtypes := make([]string, len(v))
|
||||||
|
// i := 0
|
||||||
|
// for k := range v {
|
||||||
|
// rtypes[i] = k
|
||||||
|
// i++
|
||||||
|
// }
|
||||||
|
|
||||||
|
// result += strings.Join(rtypes, "/")
|
||||||
|
// }
|
||||||
|
// errs = append(errs, Warning{fmt.Errorf("inconsistent TTLs at %q:%v", label, result)})
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Invert for a more clear error message:
|
||||||
|
for _, label := range labels {
|
||||||
|
if len(m[label]) > 1 {
|
||||||
|
r := make(map[string]map[uint32]bool)
|
||||||
|
for ttl, rtypes := range m[label] {
|
||||||
|
for rtype := range rtypes {
|
||||||
|
if _, ok := r[rtype]; !ok {
|
||||||
|
r[rtype] = make(map[uint32]bool)
|
||||||
|
}
|
||||||
|
r[rtype][ttl] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := formatInconsistency(r)
|
||||||
|
errs = append(errs, Warning{fmt.Errorf("inconsistent TTLs at %q: %s", label, result)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatInconsistency(r map[string]map[uint32]bool) string {
|
||||||
|
var rtypeResult []string
|
||||||
|
for rtype, ttlsMap := range r {
|
||||||
|
|
||||||
|
ttlList := make([]int, len(ttlsMap))
|
||||||
|
i := 0
|
||||||
|
for k := range ttlsMap {
|
||||||
|
ttlList[i] = int(k)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(ttlList)
|
||||||
|
|
||||||
|
rtypeResult = append(rtypeResult, fmt.Sprintf("%s:%v", rtype, commaSepInts(ttlList)))
|
||||||
|
}
|
||||||
|
sort.Strings(rtypeResult)
|
||||||
|
return strings.Join(rtypeResult, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func commaSepInts(list []int) string {
|
||||||
|
slist := make([]string, len(list))
|
||||||
|
for i, v := range list {
|
||||||
|
slist[i] = fmt.Sprintf("%d", v)
|
||||||
|
}
|
||||||
|
return strings.Join(slist, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// We pull this out of checkProviderCapabilities() so that it's visible within
|
// We pull this out of checkProviderCapabilities() so that it's visible within
|
||||||
// the package elsewhere, so that our test suite can look at the list of
|
// the package elsewhere, so that our test suite can look at the list of
|
||||||
// capabilities we're checking and make sure that it's up-to-date.
|
// capabilities we're checking and make sure that it's up-to-date.
|
||||||
|
@ -2,7 +2,6 @@ package normalize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/v3/models"
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||||||
@ -355,16 +354,6 @@ func TestCheckDuplicates_dup_ns(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUniq(t *testing.T) {
|
|
||||||
a := []uint32{1, 2, 2, 3, 4, 5, 5, 6}
|
|
||||||
expected := []uint32{1, 2, 3, 4, 5, 6}
|
|
||||||
|
|
||||||
r := uniq(a)
|
|
||||||
if !reflect.DeepEqual(r, expected) {
|
|
||||||
t.Error("Deduplicated slice is different than expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckLabelHasMultipleTTLs(t *testing.T) {
|
func TestCheckLabelHasMultipleTTLs(t *testing.T) {
|
||||||
records := []*models.RecordConfig{
|
records := []*models.RecordConfig{
|
||||||
// different ttl per record
|
// different ttl per record
|
||||||
|
Reference in New Issue
Block a user