mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Refactoring diff package interface (#22)
* initial refactoring of diffing * making cloudflare and others compile * gandi and gcloud. no idea if gandi works anymore. * r53 * namedotcom wasn't working.
This commit is contained in:
@@ -3,69 +3,83 @@ package diff
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
type Record interface {
|
||||
GetName() string
|
||||
GetType() string
|
||||
GetContent() string
|
||||
|
||||
// Get relevant comparision data. Default implentation uses "ttl [mx priority]", but providers may insert
|
||||
// provider specific metadata if needed.
|
||||
GetComparisionData() string
|
||||
}
|
||||
|
||||
type Correlation struct {
|
||||
Existing Record
|
||||
Desired Record
|
||||
d *differ
|
||||
Existing *models.RecordConfig
|
||||
Desired *models.RecordConfig
|
||||
}
|
||||
type Changeset []Correlation
|
||||
|
||||
func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, toDelete, modify Changeset) {
|
||||
type Differ interface {
|
||||
IncrementalDiff(existing []*models.RecordConfig) (unchanged, create, toDelete, modify Changeset)
|
||||
}
|
||||
|
||||
func New(dc *models.DomainConfig, extraValues ...func(*models.RecordConfig) map[string]string) Differ {
|
||||
return &differ{
|
||||
dc: dc,
|
||||
extraValues: extraValues,
|
||||
}
|
||||
}
|
||||
|
||||
type differ struct {
|
||||
dc *models.DomainConfig
|
||||
extraValues []func(*models.RecordConfig) map[string]string
|
||||
}
|
||||
|
||||
// get normalized content for record. target, ttl, mxprio, and specified metadata
|
||||
func (d *differ) content(r *models.RecordConfig) string {
|
||||
content := fmt.Sprintf("%s %d", r.Target, r.TTL)
|
||||
if r.Type == "MX" {
|
||||
content += fmt.Sprintf(" priority=%d", r.Priority)
|
||||
}
|
||||
|
||||
for _, f := range d.extraValues {
|
||||
for k, v := range f(r) {
|
||||
content += fmt.Sprintf(" %s=%s", k, v)
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, create, toDelete, modify Changeset) {
|
||||
unchanged = Changeset{}
|
||||
create = Changeset{}
|
||||
toDelete = Changeset{}
|
||||
modify = Changeset{}
|
||||
|
||||
// log.Printf("ID existing records: (%d)\n", len(existing))
|
||||
// for i, d := range existing {
|
||||
// log.Printf("\t%d\t%v\n", i, d)
|
||||
// }
|
||||
// log.Printf("ID desired records: (%d)\n", len(desired))
|
||||
// for i, d := range desired {
|
||||
// log.Printf("\t%d\t%v\n", i, d)
|
||||
// }
|
||||
desired := d.dc.Records
|
||||
|
||||
//sort existing and desired by name
|
||||
type key struct {
|
||||
name, rType string
|
||||
}
|
||||
existingByNameAndType := map[key][]Record{}
|
||||
desiredByNameAndType := map[key][]Record{}
|
||||
existingByNameAndType := map[key][]*models.RecordConfig{}
|
||||
desiredByNameAndType := map[key][]*models.RecordConfig{}
|
||||
for _, e := range existing {
|
||||
k := key{e.GetName(), e.GetType()}
|
||||
k := key{e.NameFQDN, e.Type}
|
||||
existingByNameAndType[k] = append(existingByNameAndType[k], e)
|
||||
}
|
||||
for _, d := range desired {
|
||||
k := key{d.GetName(), d.GetType()}
|
||||
k := key{d.NameFQDN, d.Type}
|
||||
desiredByNameAndType[k] = append(desiredByNameAndType[k], d)
|
||||
}
|
||||
|
||||
// Look through existing records. This will give us changes and deletions and some additions
|
||||
// Look through existing records. This will give us changes and deletions and some additions.
|
||||
// Each iteration is only for a single type/name record set
|
||||
for key, existingRecords := range existingByNameAndType {
|
||||
desiredRecords := desiredByNameAndType[key]
|
||||
|
||||
//first look through records that are the same content on both sides. Those are either modifications or unchanged
|
||||
|
||||
//first look through records that are the same target on both sides. Those are either modifications or unchanged
|
||||
for i := len(existingRecords) - 1; i >= 0; i-- {
|
||||
ex := existingRecords[i]
|
||||
for j, de := range desiredRecords {
|
||||
if de.GetContent() == ex.GetContent() {
|
||||
//they're either identical or should be a modification of each other
|
||||
if de.GetComparisionData() == ex.GetComparisionData() {
|
||||
unchanged = append(unchanged, Correlation{ex, de})
|
||||
if de.Target == ex.Target {
|
||||
//they're either identical or should be a modification of each other (ttl or metadata changes)
|
||||
if d.content(de) == d.content(ex) {
|
||||
unchanged = append(unchanged, Correlation{d, ex, de})
|
||||
} else {
|
||||
modify = append(modify, Correlation{ex, de})
|
||||
modify = append(modify, Correlation{d, ex, de})
|
||||
}
|
||||
// remove from both slices by index
|
||||
existingRecords = existingRecords[:i+copy(existingRecords[i:], existingRecords[i+1:])]
|
||||
@@ -75,18 +89,18 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
||||
}
|
||||
}
|
||||
|
||||
desiredLookup := map[string]Record{}
|
||||
existingLookup := map[string]Record{}
|
||||
// build index based on normalized value/ttl
|
||||
desiredLookup := map[string]*models.RecordConfig{}
|
||||
existingLookup := map[string]*models.RecordConfig{}
|
||||
// build index based on normalized content data
|
||||
for _, ex := range existingRecords {
|
||||
normalized := fmt.Sprintf("%s %s", ex.GetContent(), ex.GetComparisionData())
|
||||
normalized := d.content(ex)
|
||||
if existingLookup[normalized] != nil {
|
||||
panic(fmt.Sprintf("DUPLICATE E_RECORD FOUND: %s %s", key, normalized))
|
||||
}
|
||||
existingLookup[normalized] = ex
|
||||
}
|
||||
for _, de := range desiredRecords {
|
||||
normalized := fmt.Sprintf("%s %s", de.GetContent(), de.GetComparisionData())
|
||||
normalized := d.content(de)
|
||||
if desiredLookup[normalized] != nil {
|
||||
panic(fmt.Sprintf("DUPLICATE D_RECORD FOUND: %s %s", key, normalized))
|
||||
}
|
||||
@@ -95,36 +109,28 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
||||
// if a record is in both, it is unchanged
|
||||
for norm, ex := range existingLookup {
|
||||
if de, ok := desiredLookup[norm]; ok {
|
||||
unchanged = append(unchanged, Correlation{ex, de})
|
||||
unchanged = append(unchanged, Correlation{d, ex, de})
|
||||
delete(existingLookup, norm)
|
||||
delete(desiredLookup, norm)
|
||||
}
|
||||
}
|
||||
//sort records by normalized text. Keeps behaviour deterministic
|
||||
existingStrings, desiredStrings := []string{}, []string{}
|
||||
for norm := range existingLookup {
|
||||
existingStrings = append(existingStrings, norm)
|
||||
}
|
||||
for norm := range desiredLookup {
|
||||
desiredStrings = append(desiredStrings, norm)
|
||||
}
|
||||
sort.Strings(existingStrings)
|
||||
sort.Strings(desiredStrings)
|
||||
existingStrings, desiredStrings := sortedKeys(existingLookup), sortedKeys(desiredLookup)
|
||||
// Modifications. Take 1 from each side.
|
||||
for len(desiredStrings) > 0 && len(existingStrings) > 0 {
|
||||
modify = append(modify, Correlation{existingLookup[existingStrings[0]], desiredLookup[desiredStrings[0]]})
|
||||
modify = append(modify, Correlation{d, existingLookup[existingStrings[0]], desiredLookup[desiredStrings[0]]})
|
||||
existingStrings = existingStrings[1:]
|
||||
desiredStrings = desiredStrings[1:]
|
||||
}
|
||||
// If desired still has things they are additions
|
||||
for _, norm := range desiredStrings {
|
||||
rec := desiredLookup[norm]
|
||||
create = append(create, Correlation{nil, rec})
|
||||
create = append(create, Correlation{d, nil, rec})
|
||||
}
|
||||
// if found , but not desired, delete it
|
||||
for _, norm := range existingStrings {
|
||||
rec := existingLookup[norm]
|
||||
toDelete = append(toDelete, Correlation{rec, nil})
|
||||
toDelete = append(toDelete, Correlation{d, rec, nil})
|
||||
}
|
||||
// remove this set from the desired list to indicate we have processed it.
|
||||
delete(desiredByNameAndType, key)
|
||||
@@ -136,7 +142,7 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
||||
}
|
||||
for _, desiredList := range desiredByNameAndType {
|
||||
for _, rec := range desiredList {
|
||||
create = append(create, Correlation{nil, rec})
|
||||
create = append(create, Correlation{d, nil, rec})
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -144,10 +150,19 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
||||
|
||||
func (c Correlation) String() string {
|
||||
if c.Existing == nil {
|
||||
return fmt.Sprintf("CREATE %s %s %s %s", c.Desired.GetType(), c.Desired.GetName(), c.Desired.GetContent(), c.Desired.GetComparisionData())
|
||||
return fmt.Sprintf("CREATE %s %s %s", c.Desired.Type, c.Desired.NameFQDN, c.d.content(c.Desired))
|
||||
}
|
||||
if c.Desired == nil {
|
||||
return fmt.Sprintf("DELETE %s %s %s %s", c.Existing.GetType(), c.Existing.GetName(), c.Existing.GetContent(), c.Existing.GetComparisionData())
|
||||
return fmt.Sprintf("DELETE %s %s %s", c.Existing.Type, c.Existing.NameFQDN, c.d.content(c.Existing))
|
||||
}
|
||||
return fmt.Sprintf("MODIFY %s %s: (%s %s) -> (%s %s)", c.Existing.GetType(), c.Existing.GetName(), c.Existing.GetContent(), c.Existing.GetComparisionData(), c.Desired.GetContent(), c.Desired.GetComparisionData())
|
||||
return fmt.Sprintf("MODIFY %s %s: (%s) -> (%s)", c.Existing.Type, c.Existing.NameFQDN, c.d.content(c.Existing), c.d.content(c.Desired))
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]*models.RecordConfig) []string {
|
||||
s := []string{}
|
||||
for v := range m {
|
||||
s = append(s, v)
|
||||
}
|
||||
sort.Strings(s)
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user