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

REFACTOR: Add a backwards compatible interface to diff2 (#1870)

This commit is contained in:
Tom Limoncelli
2022-12-30 21:53:50 -05:00
committed by GitHub
parent 801aae725b
commit 397ce107e5
9 changed files with 476 additions and 67 deletions

View File

@ -123,7 +123,7 @@ func mkAdd(l string, t string, msgs []string, recs models.Records) Change {
func mkAddByLabel(l string, t string, msgs []string, newRecs models.Records) Change {
//fmt.Printf("DEBUG: mkAddByLabel: len(o)=%d len(m)=%d\n", len(newRecs), len(msgs))
//fmt.Printf("DEBUG: mkAddByLabel: msgs = %v\n", msgs)
c := Change{Type: CREATE, Msgs: msgs}
c := Change{Type: CREATE, Msgs: msgs, MsgsJoined: strings.Join(msgs, "\n")}
c.Key.NameFQDN = l
c.Key.Type = t
c.New = newRecs
@ -131,7 +131,7 @@ func mkAddByLabel(l string, t string, msgs []string, newRecs models.Records) Cha
}
func mkChange(l string, t string, msgs []string, oldRecs, newRecs models.Records) Change {
c := Change{Type: CHANGE, Msgs: msgs}
c := Change{Type: CHANGE, Msgs: msgs, MsgsJoined: strings.Join(msgs, "\n")}
c.Key.NameFQDN = l
c.Key.Type = t
c.Old = oldRecs
@ -141,7 +141,7 @@ func mkChange(l string, t string, msgs []string, oldRecs, newRecs models.Records
func mkChangeLabel(l string, t string, msgs []string, oldRecs, newRecs models.Records, msgsByKey map[models.RecordKey][]string) Change {
//fmt.Printf("DEBUG: mkChangeLabel: len(o)=%d\n", len(oldRecs))
c := Change{Type: CHANGE, Msgs: msgs}
c := Change{Type: CHANGE, Msgs: msgs, MsgsJoined: strings.Join(msgs, "\n")}
c.Key.NameFQDN = l
c.Key.Type = t
c.Old = oldRecs
@ -151,14 +151,14 @@ func mkChangeLabel(l string, t string, msgs []string, oldRecs, newRecs models.Re
}
func mkDelete(l string, t string, oldRecs models.Records, msgs []string) Change {
c := Change{Type: DELETE, Msgs: msgs}
c := Change{Type: DELETE, Msgs: msgs, MsgsJoined: strings.Join(msgs, "\n")}
c.Key.NameFQDN = l
c.Key.Type = t
c.Old = oldRecs
return c
}
func mkDeleteRec(l string, t string, msgs []string, rec *models.RecordConfig) Change {
c := Change{Type: DELETE, Msgs: msgs}
c := Change{Type: DELETE, Msgs: msgs, MsgsJoined: strings.Join(msgs, "\n")}
c.Key.NameFQDN = l
c.Key.Type = t
c.Old = models.Records{rec}
@ -175,16 +175,70 @@ func removeCommon(existing, desired []targetConfig) ([]targetConfig, []targetCon
eKeys := map[string]*targetConfig{}
for _, v := range existing {
v := v
eKeys[v.compareable] = &v
}
dKeys := map[string]*targetConfig{}
for _, v := range desired {
v := v
dKeys[v.compareable] = &v
}
return filterBy(existing, dKeys), filterBy(desired, eKeys)
}
// Find the changes that are exclusively changes in TTL.
func splitTTLOnly(existing, desired []targetConfig) (
existDiff []targetConfig, desireDiff []targetConfig,
existTTL models.Records, desireTTL models.Records,
) {
ei := 0
di := 0
for (ei < len(existing)) && (di < len(desired)) {
er := existing[ei].rec
dr := desired[di].rec
ecomp := er.GetTargetCombined()
dcomp := dr.GetTargetCombined()
if ecomp == dcomp && er.TTL == dr.TTL {
panic("Should not happen. There should be some difference!")
}
//fmt.Printf("DEBUG ecomp=%q dcomp=%q ettl=%d dttl=%d\n", ecomp, dcomp, er.TTL, dr.TTL)
if ecomp == dcomp && er.TTL != dr.TTL {
//fmt.Printf("DEBUG: equal\n")
existTTL = append(existTTL, er)
desireTTL = append(desireTTL, dr)
ei++
di++
} else if ecomp < dcomp {
//fmt.Printf("DEBUG: less\n")
existDiff = append(existDiff, existing[ei])
ei++
} else if ecomp > dcomp {
//fmt.Printf("DEBUG: greater\n")
desireDiff = append(desireDiff, desired[di])
di++
} else {
panic("Should not happen. e and d can not be both equal, less and greater")
}
}
// Any remainder goes to the *Diff result:
if ei < len(existing) {
//fmt.Printf("DEBUG: append e len()=%d\n", ei)
existDiff = append(existDiff, existing[ei:]...)
}
if di < len(desired) {
//fmt.Printf("DEBUG: append d len()=%d\n", di)
desireDiff = append(desireDiff, desired[di:]...)
}
return
}
// Return s but remove any items that can be found in m.
func filterBy(s []targetConfig, m map[string]*targetConfig) []targetConfig {
// fmt.Printf("DEBUG: filterBy called with %v\n", s)
@ -212,6 +266,22 @@ func filterBy(s []targetConfig, m map[string]*targetConfig) []targetConfig {
return s
}
func humanDiff(a, b *models.RecordConfig) string {
acombined := a.GetTargetCombined()
bcombined := b.GetTargetCombined()
combinedDiff := acombined != bcombined
ttlDiff := a.TTL != b.TTL
// TODO(tlim): It would be nice if we included special cases for MX
// records and others.
if combinedDiff && ttlDiff {
return fmt.Sprintf("(%s ttl=%d) -> (%s ttl=%d)", acombined, a.TTL, bcombined, b.TTL)
}
if combinedDiff {
return fmt.Sprintf("(%s) -> (%s)", acombined, bcombined)
}
return fmt.Sprintf("%s (ttl %d->%d)", acombined, a.TTL, b.TTL)
}
func diffTargets(existing, desired []targetConfig) ChangeList {
//fmt.Printf("DEBUG: diffTargets called with len(e)=%d len(d)=%d\n", len(existing), len(desired))
@ -230,17 +300,35 @@ func diffTargets(existing, desired []targetConfig) ChangeList {
// remove the exact matches.
existing, desired = removeCommon(existing, desired)
// the common chunk are changes
// At this point the exact matches are removed. However there may be
// records that have the same GetTargetCombined() but different
// TTLs.
// Find TTL changes:
existing, desired, existingTTL, desiredTTL := splitTTLOnly(existing, desired)
for i := range desiredTTL {
er := existingTTL[i]
dr := desiredTTL[i]
m := fmt.Sprintf("CHANGE %s %s ", dr.NameFQDN, dr.Type) + humanDiff(er, dr)
instructions = append(instructions, mkChange(dr.NameFQDN, dr.Type, []string{m},
models.Records{er},
models.Records{dr},
))
}
// the common chunk are changes (regardless of TTL)
mi := min(len(existing), len(desired))
//fmt.Printf("DEBUG: min=%d\n", mi)
for i := 0; i < mi; i++ {
//fmt.Println(i, "CHANGE")
er := existing[i].rec
dr := desired[i].rec
m := fmt.Sprintf("CHANGE %s %s (%s) -> (%s)", dr.NameFQDN, dr.Type, er.GetTargetCombined(), dr.GetTargetCombined())
m := fmt.Sprintf("CHANGE %s %s ", dr.NameFQDN, dr.Type) + humanDiff(existing[i].rec, desired[i].rec)
instructions = append(instructions, mkChange(dr.NameFQDN, dr.Type, []string{m},
//models.Records{existing[i].rec},
//models.Records{desired[i].rec},
models.Records{er},
models.Records{dr},
))

View File

@ -9,7 +9,7 @@ import (
"github.com/kylelemons/godebug/diff"
)
var testDataAA1234 = makeRec("laba", "A", "1.2.3.4") // [0]
var testDataAA1234 = makeRec("laba", "A", "1.2.3.4") // [0]
var testDataAA5678 = makeRec("laba", "A", "5.6.7.8") //
var testDataAA1234ttl700 = makeRecTTL("laba", "A", "1.2.3.4", 700) //
var testDataAA5678ttl700 = makeRecTTL("laba", "A", "5.6.7.8", 700) //
@ -42,8 +42,6 @@ var d11 = makeRec("labg", "NS", "10.10.10.97") // [11']
var d12 = makeRec("labh", "A", "1.2.3.4") // [12']
var testDataApexMX22bbb = makeRec("", "MX", "22 bbb")
var d0tc = mkTargetConfig(testDataAA1234clone)
func makeChange(v Verb, l, t string, old, new models.Records, msgs []string) Change {
c := Change{
Type: v,
@ -425,6 +423,7 @@ func mkTargetConfigMap(x ...*models.RecordConfig) map[string]*targetConfig {
}
func Test_diffTargets(t *testing.T) {
type args struct {
existing []targetConfig
desired []targetConfig
@ -435,6 +434,24 @@ func Test_diffTargets(t *testing.T) {
want ChangeList
}{
{
name: "add1changettl",
args: args{
existing: mkTargetConfig(testDataAA5678),
desired: mkTargetConfig(testDataAA5678ttl700, testDataAA1234ttl700),
},
want: ChangeList{
Change{Type: CHANGE,
Key: models.RecordKey{NameFQDN: "laba.f.com", Type: "A"},
New: models.Records{testDataAA5678ttl700, testDataAA1234ttl700},
Msgs: []string{
"CHANGE laba.f.com A 5.6.7.8 (ttl 300->700)",
"CREATE laba.f.com A 1.2.3.4",
},
},
},
},
{
name: "single",
args: args{
@ -530,15 +547,26 @@ func Test_removeCommon(t *testing.T) {
want []targetConfig
want1 []targetConfig
}{
{
name: "same",
args: args{
existing: d0tc,
desired: d0tc,
existing: mkTargetConfig(testDataAA1234clone),
desired: mkTargetConfig(testDataAA1234clone),
},
want: []targetConfig{},
want1: []targetConfig{},
},
{
name: "disjoint",
args: args{
existing: mkTargetConfig(testDataAA1234),
desired: mkTargetConfig(testDataAA5678),
},
want: mkTargetConfig(testDataAA1234),
want1: mkTargetConfig(testDataAA5678),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -607,3 +635,60 @@ func Test_filterBy(t *testing.T) {
})
}
}
func Test_splitTTLOnly(t *testing.T) {
type args struct {
existing []targetConfig
desired []targetConfig
}
tests := []struct {
name string
args args
wantExistDiff []targetConfig
wantDesireDiff []targetConfig
wantExistTTL models.Records
wantDesireTTL models.Records
}{
{
name: "simple",
args: args{
existing: mkTargetConfig(testDataAA1234),
desired: mkTargetConfig(testDataAA1234ttl700),
},
wantExistDiff: mkTargetConfig(),
wantDesireDiff: mkTargetConfig(),
wantExistTTL: models.Records{testDataAA1234},
wantDesireTTL: models.Records{testDataAA1234ttl700},
},
{
name: "both",
args: args{
existing: mkTargetConfig(testDataAA1234),
desired: mkTargetConfig(testDataAA5678),
},
wantExistDiff: mkTargetConfig(testDataAA1234),
wantDesireDiff: mkTargetConfig(testDataAA5678),
wantExistTTL: models.Records{},
wantDesireTTL: models.Records{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExistDiff, gotDesireDiff, gotExistTTL, gotDesireTTL := splitTTLOnly(tt.args.existing, tt.args.desired)
if !reflect.DeepEqual(gotExistDiff, tt.wantExistDiff) {
t.Errorf("splitTTLOnly() gotExistDiff = %v, want %v", gotExistDiff, tt.wantExistDiff)
}
if !reflect.DeepEqual(gotDesireDiff, tt.wantDesireDiff) {
t.Errorf("splitTTLOnly() gotDesireDiff = %v, want %v", gotDesireDiff, tt.wantDesireDiff)
}
if ((len(tt.wantExistTTL) != 0) && len(gotExistTTL) != 0) && !reflect.DeepEqual(gotExistTTL, tt.wantExistTTL) {
t.Errorf("splitTTLOnly() gotExistTTL = %v, want %v (len=%d %d)", gotExistTTL, tt.wantExistTTL, len(gotExistTTL), len(tt.wantExistTTL))
}
if ((len(tt.wantDesireTTL) != 0) && len(gotDesireTTL) != 0) && !reflect.DeepEqual(gotDesireTTL, tt.wantDesireTTL) {
t.Errorf("splitTTLOnly() gotDesireTTL = %v, want %v", gotDesireTTL, tt.wantDesireTTL)
}
})
}
}

View File

@ -202,10 +202,12 @@ func (cc *CompareConfig) addRecords(recs models.Records, storeInExisting bool) {
//fmt.Printf("BEFORE E/D: %v/%v\n", len(td.existingRecs), len(td.desiredRecs))
if storeInExisting {
cc.ldata[labelIdx].tdata[rtIdx].existingRecs = append(cc.ldata[labelIdx].tdata[rtIdx].existingRecs, rec)
cc.ldata[labelIdx].tdata[rtIdx].existingTargets = append(cc.ldata[labelIdx].tdata[rtIdx].existingTargets, targetConfig{compareable: comp, rec: rec})
cc.ldata[labelIdx].tdata[rtIdx].existingTargets = append(cc.ldata[labelIdx].tdata[rtIdx].existingTargets,
targetConfig{compareable: comp, rec: rec})
} else {
cc.ldata[labelIdx].tdata[rtIdx].desiredRecs = append(cc.ldata[labelIdx].tdata[rtIdx].desiredRecs, rec)
cc.ldata[labelIdx].tdata[rtIdx].desiredTargets = append(cc.ldata[labelIdx].tdata[rtIdx].desiredTargets, targetConfig{compareable: comp, rec: rec})
cc.ldata[labelIdx].tdata[rtIdx].desiredTargets = append(cc.ldata[labelIdx].tdata[rtIdx].desiredTargets,
targetConfig{compareable: comp, rec: rec})
}
//fmt.Printf("AFTER L: %v\n", len(cc.ldata))
//fmt.Printf("AFTER E/D: %v/%v\n", len(td.existingRecs), len(td.desiredRecs))

85
pkg/diff2/compat.go Normal file
View File

@ -0,0 +1,85 @@
package diff2
// import (
// "github.com/StackExchange/dnscontrol/v3/models"
// "github.com/StackExchange/dnscontrol/v3/pkg/diff"
// )
// // Provide an interface that is backwards compatible with pkg/diff.
// // // /diff/IncrementalDiffCompat(). It not as efficient as converting
// // to the diff2.By*() functions. However, using this is better than
// // staying with pkg/diff.
// func CompatIncrementalDiff(dc *models.DomainConfig, existing []*models.RecordConfig) (unchanged, create, toDelete, modify diff.Changeset, err error) {
// unchanged = diff.Changeset{}
// create = diff.Changeset{}
// toDelete = diff.Changeset{}
// modify = diff.Changeset{}
// instructions, err := ByRecord(existing, dc, nil)
// if err != nil {
// return nil, nil, nil, nil, err
// }
// d := diff.New(dc, nil)
// for _, inst := range instructions {
// //cor := Correlation{d: d}
// cor := diff.Correlation{d: d}
// switch inst.Type {
// case CREATE:
// cor.Desired = inst.New[0]
// create = append(create, cor)
// case CHANGE:
// cor.Existing = inst.Old[0]
// cor.Desired = inst.New[0]
// modify = append(modify, cor)
// case DELETE:
// cor.Existing = inst.Old[0]
// toDelete = append(toDelete, cor)
// }
// }
// return
// }
// // func (d *Differ) ChangedGroups(existing []*models.RecordConfig) (map[models.RecordKey][]string, error) {
// // changedKeys := map[models.RecordKey][]string{}
// // _, create, toDelete, modify, err := d.IncrementalDiff(existing)
// // if err != nil {
// // return nil, err
// // }
// // for _, c := range create {
// // changedKeys[c.Desired.Key()] = append(changedKeys[c.Desired.Key()], c.String())
// // }
// // for _, d := range toDelete {
// // changedKeys[d.Existing.Key()] = append(changedKeys[d.Existing.Key()], d.String())
// // }
// // for _, m := range modify {
// // changedKeys[m.Desired.Key()] = append(changedKeys[m.Desired.Key()], m.String())
// // }
// // return changedKeys, nil
// // }
// // func (c Correlation) String() string {
// // if c.Existing == nil {
// // return fmt.Sprintf("CREATE %s %s %s", c.Desired.Type, c.Desired.GetLabelFQDN(), c.d.content(c.Desired))
// // }
// // if c.Desired == nil {
// // return fmt.Sprintf("DELETE %s %s %s", c.Existing.Type, c.Existing.GetLabelFQDN(), c.d.content(c.Existing))
// // }
// // return fmt.Sprintf("MODIFY %s %s: (%s) -> (%s)", c.Existing.Type, c.Existing.GetLabelFQDN(), c.d.content(c.Existing), c.d.content(c.Desired))
// // }
// // // get normalized content for record. target, ttl, mxprio, and specified metadata
// // func (d *Differ) content(r *models.RecordConfig) string {
// // // get the extra values maps to add to the comparison.
// // var allMaps []map[string]string
// // for _, f := range d.extraValues {
// // valueMap := f(r)
// // allMaps = append(allMaps, valueMap)
// // }
// // return r.ToDiffable(allMaps...)
// // }

View File

@ -26,13 +26,59 @@ type ChangeList []Change
type Change struct {
Type Verb // Add, Change, Delete
Key models.RecordKey // .Key.Type is "" unless using ByRecordSet
Old models.Records
New models.Records // any changed or added records at Key.
Msgs []string // Human-friendly explanation of what changed
MsgsByKey map[models.RecordKey][]string // Messages for a given key
Key models.RecordKey // .Key.Type is "" unless using ByRecordSet
Old models.Records
New models.Records // any changed or added records at Key.
Msgs []string // Human-friendly explanation of what changed
MsgsJoined string // strings.Join(Msgs, "\n")
MsgsByKey map[models.RecordKey][]string // Messages for a given key
}
/*
General instructions:
changes, err := diff2.ByRecord(existing, dc, nil)
//changes, err := diff2.ByRecordSet(existing, dc, nil)
//changes, err := diff2.ByLabel(existing, dc, nil)
if err != nil {
return nil, err
}
var corrections []*models.Correction
for _, change := range changes {
switch change.Type {
case diff2.CREATE:
corr = &models.Correction{
Msg: change.MsgsJoined,
F: func() error {
return c.createRecord(FILL IN)
},
}
case diff2.CHANGE:
corr = &models.Correction{
Msg: change.MsgsJoined,
F: func() error {
return c.modifyRecord(FILL IN)
},
}
case diff2.DELETE:
corr = &models.Correction{
Msg: change.MsgsJoined,
F: func() error {
return c.deleteRecord(FILL IN)
},
}
}
corrections = append(corrections, corr)
}
return corrections, nil
}
*/
// ByRecordSet takes two lists of records (existing and desired) and
// returns instructions for turning existing into desired.
//
@ -143,18 +189,6 @@ func ByZone(existing models.Records, dc *models.DomainConfig, compFunc Comparabl
return justMsgs(instructions), len(instructions) != 0, nil
}
/*
nil, nil :
nil, nonzero : nil, true, nil
nonzero, nil : msgs, true, nil
nonzero, nonzero :
existing: changes : return msgs, true, nil
existing: no changes : return nil, false, nil
not existing: no changes: return nil, false, nil
not existing: changes : return nil, true, nil
*/
func (c Change) String() string {
var buf bytes.Buffer
b := &buf