mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-11-10 17:26:10 +08:00
186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
|
package diff2
|
||
|
|
||
|
//go:generate stringer -type=Verb
|
||
|
|
||
|
// This module provides functions that "diff" the existing records
|
||
|
// against the desired records.
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/StackExchange/dnscontrol/v3/models"
|
||
|
)
|
||
|
|
||
|
type Verb int
|
||
|
|
||
|
const (
|
||
|
_ Verb = iota // Skip the first value of 0
|
||
|
CREATE // Create a record/recordset/label where none existed before.
|
||
|
CHANGE // Change existing record/recordset/label
|
||
|
DELETE // Delete existing record/recordset/label
|
||
|
)
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// ByRecordSet takes two lists of records (existing and desired) and
|
||
|
// returns instructions for turning existing into desired.
|
||
|
//
|
||
|
// Use this with DNS providers whose API updates one recordset at a
|
||
|
// time. A recordset is all the records of a particular type at a
|
||
|
// label. For example, if www.example.com has 3 A records and a TXT
|
||
|
// record, if A records are added, changed, or removed, the API takes
|
||
|
// www.example.com, A, and a list of all the desired IP addresses.
|
||
|
//
|
||
|
// Examples include:
|
||
|
func ByRecordSet(existing models.Records, dc *models.DomainConfig, compFunc ComparableFunc) (ChangeList, error) {
|
||
|
// dc stores the desired state.
|
||
|
|
||
|
desired := dc.Records
|
||
|
var err error
|
||
|
desired, err = handsoff(existing, desired, dc.Unmanaged, dc.UnmanagedUnsafe) // Handle UNMANAGED()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
cc := NewCompareConfig(dc.Name, existing, desired, compFunc)
|
||
|
instructions := analyzeByRecordSet(cc)
|
||
|
return processPurge(instructions, !dc.KeepUnknown), nil
|
||
|
}
|
||
|
|
||
|
// ByLabel takes two lists of records (existing and desired) and
|
||
|
// returns instructions for turning existing into desired.
|
||
|
//
|
||
|
// Use this with DNS providers whose API updates one label at a
|
||
|
// time. That is, updates are done by sending a list of DNS records
|
||
|
// to be served at a particular label, or the label itself is deleted.
|
||
|
//
|
||
|
// Examples include:
|
||
|
func ByLabel(existing models.Records, dc *models.DomainConfig, compFunc ComparableFunc) (ChangeList, error) {
|
||
|
// dc stores the desired state.
|
||
|
|
||
|
desired := dc.Records
|
||
|
var err error
|
||
|
desired, err = handsoff(existing, desired, dc.Unmanaged, dc.UnmanagedUnsafe) // Handle UNMANAGED()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
cc := NewCompareConfig(dc.Name, existing, desired, compFunc)
|
||
|
instructions := analyzeByLabel(cc)
|
||
|
return processPurge(instructions, !dc.KeepUnknown), nil
|
||
|
}
|
||
|
|
||
|
// ByRecord takes two lists of records (existing and desired) and
|
||
|
// returns instructions for turning existing into desired.
|
||
|
//
|
||
|
// Use this with DNS providers whose API updates one record at a time.
|
||
|
//
|
||
|
// Examples include: INWX
|
||
|
func ByRecord(existing models.Records, dc *models.DomainConfig, compFunc ComparableFunc) (ChangeList, error) {
|
||
|
// dc stores the desired state.
|
||
|
|
||
|
desired := dc.Records
|
||
|
var err error
|
||
|
desired, err = handsoff(existing, desired, dc.Unmanaged, dc.UnmanagedUnsafe) // Handle UNMANAGED()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
cc := NewCompareConfig(dc.Name, existing, desired, compFunc)
|
||
|
instructions := analyzeByRecord(cc)
|
||
|
return processPurge(instructions, !dc.KeepUnknown), nil
|
||
|
}
|
||
|
|
||
|
// ByZone takes two lists of records (existing and desired) and
|
||
|
// returns text one would output to users describing the change.
|
||
|
//
|
||
|
// Use this with DNS providers whose API updates the entire zone at a
|
||
|
// time. That is, to make any change (1 record or many) the entire DNS
|
||
|
// zone is uploaded.
|
||
|
//
|
||
|
// The user should see a list of changes as if individual records were
|
||
|
// updated.
|
||
|
//
|
||
|
// The caller of this function should:
|
||
|
//
|
||
|
// changed, msgs := diff2.ByZone(existing, desired, origin, nil
|
||
|
// fmt.Sprintf("CREATING ZONEFILE FOR THE FIRST TIME: dir/example.com.zone"))
|
||
|
// if changed {
|
||
|
// // output msgs
|
||
|
// // generate the zone using the "desired" records
|
||
|
// }
|
||
|
//
|
||
|
// Example providers include: BIND
|
||
|
func ByZone(existing models.Records, dc *models.DomainConfig, compFunc ComparableFunc) ([]string, bool, error) {
|
||
|
// dc stores the desired state.
|
||
|
|
||
|
if len(existing) == 0 {
|
||
|
// Nothing previously existed. No need to output a list of individual changes.
|
||
|
return nil, true, nil
|
||
|
}
|
||
|
|
||
|
desired := dc.Records
|
||
|
var err error
|
||
|
desired, err = handsoff(existing, desired, dc.Unmanaged, dc.UnmanagedUnsafe) // Handle UNMANAGED()
|
||
|
if err != nil {
|
||
|
return nil, false, err
|
||
|
}
|
||
|
|
||
|
cc := NewCompareConfig(dc.Name, existing, desired, compFunc)
|
||
|
instructions := analyzeByRecord(cc)
|
||
|
instructions = processPurge(instructions, !dc.KeepUnknown)
|
||
|
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
|
||
|
|
||
|
fmt.Fprintf(b, "Change: verb=%v\n", c.Type)
|
||
|
fmt.Fprintf(b, " key=%v\n", c.Key)
|
||
|
if len(c.Old) != 0 {
|
||
|
fmt.Fprintf(b, " old=%v\n", c.Old)
|
||
|
}
|
||
|
if len(c.New) != 0 {
|
||
|
fmt.Fprintf(b, " new=%v\n", c.New)
|
||
|
}
|
||
|
fmt.Fprintf(b, " msg=%q\n", c.Msgs)
|
||
|
|
||
|
return b.String()
|
||
|
}
|
||
|
|
||
|
func (cl ChangeList) String() string {
|
||
|
var buf bytes.Buffer
|
||
|
b := &buf
|
||
|
|
||
|
fmt.Fprintf(b, "ChangeList: len=%d\n", len(cl))
|
||
|
for i, j := range cl {
|
||
|
fmt.Fprintf(b, "%02d: %s", i, j)
|
||
|
}
|
||
|
|
||
|
return b.String()
|
||
|
}
|