mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-04 12:14:23 +08: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:
parent
1f8b0a11e0
commit
12f006441b
15 changed files with 322 additions and 391 deletions
|
@ -66,23 +66,19 @@ type RecordConfig struct {
|
|||
Metadata map[string]string `json:"meta,omitempty"`
|
||||
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
||||
Priority uint16 `json:"priority,omitempty"`
|
||||
|
||||
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
|
||||
}
|
||||
|
||||
func (r *RecordConfig) GetName() string {
|
||||
return r.NameFQDN
|
||||
}
|
||||
func (r *RecordConfig) GetType() string {
|
||||
return r.Type
|
||||
}
|
||||
func (r *RecordConfig) GetContent() string {
|
||||
return r.Target
|
||||
}
|
||||
func (r *RecordConfig) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
func (r *RecordConfig) String() string {
|
||||
content := fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL)
|
||||
if r.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %d ", r.Priority)
|
||||
content += fmt.Sprintf(" priority=%d", r.Priority)
|
||||
}
|
||||
return fmt.Sprintf("%d%s", r.TTL, mxPrio)
|
||||
for k, v := range r.Metadata {
|
||||
content += fmt.Sprintf(" %s=%s", k, v)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
/// Convert RecordConfig -> dns.RR.
|
||||
|
|
|
@ -200,6 +200,9 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
|||
|
||||
// Normalize Records.
|
||||
for _, rec := range domain.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = models.DefaultTTL
|
||||
}
|
||||
// Validate the unmodified inputs:
|
||||
if err := validateRecordTypes(rec, domain.Name); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
|
|
@ -38,23 +38,8 @@ func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
|
|||
return nil, fmt.Errorf("c.getExistingRecords(%v) failed: %v", dc.Name, err)
|
||||
}
|
||||
|
||||
// Read expectedRecords:
|
||||
//expectedRecords := make([]*models.RecordConfig, len(dc.Records))
|
||||
expectedRecords := make([]diff.Record, len(dc.Records))
|
||||
for i, r := range dc.Records {
|
||||
if r.TTL == 0 {
|
||||
r.TTL = models.DefaultTTL
|
||||
}
|
||||
expectedRecords[i] = r
|
||||
}
|
||||
|
||||
// Convert to []diff.Records and compare:
|
||||
foundDiffRecords := make([]diff.Record, 0, len(foundRecords))
|
||||
for _, rec := range foundRecords {
|
||||
foundDiffRecords = append(foundDiffRecords, rec)
|
||||
}
|
||||
|
||||
_, creates, dels, modifications := diff.IncrementalDiff(foundDiffRecords, expectedRecords)
|
||||
differ := diff.New(dc)
|
||||
_, creates, dels, modifications := differ.IncrementalDiff(foundRecords)
|
||||
// NOTE(tlim): This provider does not delete records. If
|
||||
// you need to delete a record, either delete it manually
|
||||
// or see providers/activedir/doc.md for implementation tips.
|
||||
|
@ -65,10 +50,10 @@ func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
|
|||
if dc.KeepUnknown {
|
||||
break
|
||||
}
|
||||
corrections = append(corrections, c.deleteRec(dc.Name, del.Existing.(*models.RecordConfig)))
|
||||
corrections = append(corrections, c.deleteRec(dc.Name, del.Existing))
|
||||
}
|
||||
for _, cre := range creates {
|
||||
corrections = append(corrections, c.createRec(dc.Name, cre.Desired.(*models.RecordConfig))...)
|
||||
corrections = append(corrections, c.createRec(dc.Name, cre.Desired)...)
|
||||
}
|
||||
for _, m := range modifications {
|
||||
corrections = append(corrections, c.modifyRec(dc.Name, m))
|
||||
|
@ -298,15 +283,11 @@ func (c *adProvider) createRec(domainname string, rec *models.RecordConfig) []*m
|
|||
}
|
||||
|
||||
func (c *adProvider) modifyRec(domainname string, m diff.Correlation) *models.Correction {
|
||||
|
||||
old, rec := m.Existing.(*models.RecordConfig), m.Desired.(*models.RecordConfig)
|
||||
oldContent := old.GetContent()
|
||||
newContent := rec.GetContent()
|
||||
|
||||
old, rec := m.Existing, m.Desired
|
||||
return &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return powerShellDoCommand(c.generatePowerShellModify(domainname, rec.Name, rec.Type, oldContent, newContent, old.TTL, rec.TTL))
|
||||
return powerShellDoCommand(c.generatePowerShellModify(domainname, rec.Name, rec.Type, old.Target, rec.Target, old.TTL, rec.TTL))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,14 +158,6 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
|
|||
// Default SOA record. If we see one in the zone, this will be replaced.
|
||||
soa_rec := makeDefaultSOA(c.Default_Soa, dc.Name)
|
||||
|
||||
// Read expectedRecords:
|
||||
expectedRecords := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||
for _, r := range dc.Records {
|
||||
if r.TTL == 0 {
|
||||
r.TTL = models.DefaultTTL
|
||||
}
|
||||
expectedRecords = append(expectedRecords, r)
|
||||
}
|
||||
// Read foundRecords:
|
||||
foundRecords := make([]*models.RecordConfig, 0)
|
||||
var old_serial, new_serial uint32
|
||||
|
@ -198,22 +190,13 @@ func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correcti
|
|||
}
|
||||
}
|
||||
|
||||
// Add SOA record:
|
||||
// Add SOA record to expected set:
|
||||
if !dc.HasRecordTypeName("SOA", "@") {
|
||||
expectedRecords = append(expectedRecords, soa_rec)
|
||||
dc.Records = append(dc.Records, soa_rec)
|
||||
}
|
||||
|
||||
// Convert to []diff.Records and compare:
|
||||
foundDiffRecords := make([]diff.Record, len(foundRecords))
|
||||
for i := range foundRecords {
|
||||
foundDiffRecords[i] = foundRecords[i]
|
||||
}
|
||||
expectedDiffRecords := make([]diff.Record, len(expectedRecords))
|
||||
for i := range expectedRecords {
|
||||
expectedDiffRecords[i] = expectedRecords[i]
|
||||
}
|
||||
_, create, del, mod := diff.IncrementalDiff(foundDiffRecords, expectedDiffRecords)
|
||||
differ := diff.New(dc)
|
||||
_, create, del, mod := differ.IncrementalDiff(foundRecords)
|
||||
|
||||
// Print a list of changes. Generate an actual change that is the zone
|
||||
changes := false
|
||||
|
|
|
@ -31,7 +31,6 @@ Domain level metadata availible:
|
|||
|
||||
Provider level metadata availible:
|
||||
- ip_conversions
|
||||
- secret_ips
|
||||
*/
|
||||
|
||||
type CloudflareApi struct {
|
||||
|
@ -40,7 +39,6 @@ type CloudflareApi struct {
|
|||
domainIndex map[string]string
|
||||
nameservers map[string][]string
|
||||
ipConversions []transform.IpConversion
|
||||
secretIPs []net.IP
|
||||
ignoredLabels []string
|
||||
}
|
||||
|
||||
|
@ -79,7 +77,7 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||
if err := c.preprocessConfig(dc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records, err := c.getRecordsForDomain(id)
|
||||
records, err := c.getRecordsForDomain(id, dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,50 +85,52 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||
for i := len(records) - 1; i >= 0; i-- {
|
||||
rec := records[i]
|
||||
// Delete ignore labels
|
||||
if labelMatches(dnsutil.TrimDomainName(rec.(*cfRecord).Name, dc.Name), c.ignoredLabels) {
|
||||
fmt.Printf("ignored_label: %s\n", rec.(*cfRecord).Name)
|
||||
if labelMatches(dnsutil.TrimDomainName(rec.Original.(*cfRecord).Name, dc.Name), c.ignoredLabels) {
|
||||
fmt.Printf("ignored_label: %s\n", rec.Original.(*cfRecord).Name)
|
||||
records = append(records[:i], records[i+1:]...)
|
||||
}
|
||||
//normalize cname,mx,ns records with dots to be consistent with our config format.
|
||||
t := rec.(*cfRecord).Type
|
||||
if t == "CNAME" || t == "MX" || t == "NS" {
|
||||
rec.(*cfRecord).Content = dnsutil.AddOrigin(rec.(*cfRecord).Content+".", dc.Name)
|
||||
}
|
||||
}
|
||||
|
||||
expectedRecords := make([]diff.Record, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if labelMatches(rec.Name, c.ignoredLabels) {
|
||||
log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.Name, c.ignoredLabels)
|
||||
// Since we log.Fatalf, we don't need to be clean here.
|
||||
}
|
||||
}
|
||||
checkNSModifications(dc)
|
||||
differ := diff.New(dc, getProxyMetadata)
|
||||
_, create, del, mod := differ.IncrementalDiff(records)
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, d := range del {
|
||||
corrections = append(corrections, c.deleteRec(d.Existing.Original.(*cfRecord), id))
|
||||
}
|
||||
for _, d := range create {
|
||||
corrections = append(corrections, c.createRec(d.Desired, id)...)
|
||||
}
|
||||
|
||||
for _, d := range mod {
|
||||
e, rec := d.Existing.Original.(*cfRecord), d.Desired
|
||||
proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: d.String(),
|
||||
F: func() error { return c.modifyRecord(id, e.ID, proxy, rec) },
|
||||
})
|
||||
}
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func checkNSModifications(dc *models.DomainConfig) {
|
||||
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||
if !strings.HasSuffix(rec.Target, ".ns.cloudflare.com.") {
|
||||
log.Printf("Warning: cloudflare does not support modifying NS records on base domain. %s will not be added.", rec.Target)
|
||||
}
|
||||
continue
|
||||
}
|
||||
expectedRecords = append(expectedRecords, recordWrapper{rec})
|
||||
newList = append(newList, rec)
|
||||
}
|
||||
_, create, del, mod := diff.IncrementalDiff(records, expectedRecords)
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, d := range del {
|
||||
corrections = append(corrections, c.deleteRec(d.Existing.(*cfRecord), id))
|
||||
}
|
||||
for _, d := range create {
|
||||
corrections = append(corrections, c.createRec(d.Desired.(recordWrapper).RecordConfig, id)...)
|
||||
}
|
||||
|
||||
for _, d := range mod {
|
||||
e, rec := d.Existing.(*cfRecord), d.Desired.(recordWrapper)
|
||||
proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: fmt.Sprintf("MODIFY record %s %s: (%s %s) => (%s %s)", rec.Name, rec.Type, e.Content, e.GetComparisionData(), rec.Target, rec.GetComparisionData()),
|
||||
F: func() error { return c.modifyRecord(id, e.ID, proxy, rec.RecordConfig) },
|
||||
})
|
||||
}
|
||||
return corrections, nil
|
||||
dc.Records = newList
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -138,7 +138,6 @@ const (
|
|||
metaProxyDefault = metaProxy + "_default"
|
||||
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
|
||||
metaIPConversions = "ip_conversions" // TODO(tlim): Rename to obscure_rules.
|
||||
metaSecretIPs = "secret_ips" // TODO(tlim): Rename to obscured_cidrs.
|
||||
)
|
||||
|
||||
func checkProxyVal(v string) (string, error) {
|
||||
|
@ -167,6 +166,9 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
|
|||
// A and CNAMEs: Validate. If null, set to default.
|
||||
// else: Make sure it wasn't set. Set to default.
|
||||
for _, rec := range dc.Records {
|
||||
if rec.TTL == 0 || rec.TTL == 300 {
|
||||
rec.TTL = 1
|
||||
}
|
||||
if rec.Type != "A" && rec.Type != "CNAME" && rec.Type != "AAAA" {
|
||||
if rec.Metadata[metaProxy] != "" {
|
||||
return fmt.Errorf("cloudflare_proxy set on %v record: %#v cloudflare_proxy=%#v", rec.Type, rec.Name, rec.Metadata[metaProxy])
|
||||
|
@ -188,9 +190,6 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
|
|||
|
||||
// look for ip conversions and transform records
|
||||
for _, rec := range dc.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = 1
|
||||
}
|
||||
if rec.Type != "A" {
|
||||
continue
|
||||
}
|
||||
|
@ -223,9 +222,8 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||
|
||||
if len(metadata) > 0 {
|
||||
parsedMeta := &struct {
|
||||
IPConversions string `json:"ip_conversions"`
|
||||
SecretIps []interface{} `json:"secret_ips"`
|
||||
IgnoredLabels []string `json:"ignored_labels"`
|
||||
IPConversions string `json:"ip_conversions"`
|
||||
IgnoredLabels []string `json:"ignored_labels"`
|
||||
}{}
|
||||
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
||||
if err != nil {
|
||||
|
@ -240,15 +238,6 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := []net.IP{}
|
||||
for _, ipStr := range parsedMeta.SecretIps {
|
||||
var ip net.IP
|
||||
if ip, err = models.InterfaceToIP(ipStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
api.secretIPs = ips
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
@ -265,58 +254,42 @@ type cfRecord struct {
|
|||
Content string `json:"content"`
|
||||
Proxiable bool `json:"proxiable"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TTL int `json:"ttl"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
Locked bool `json:"locked"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
ZoneName string `json:"zone_name"`
|
||||
CreatedOn time.Time `json:"created_on"`
|
||||
ModifiedOn time.Time `json:"modified_on"`
|
||||
Data interface{} `json:"data"`
|
||||
Priority int `json:"priority"`
|
||||
Priority uint16 `json:"priority"`
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetType() string {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetContent() string {
|
||||
return c.Content
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if c.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %d ", c.Priority)
|
||||
func (c *cfRecord) toRecord(domain string) *models.RecordConfig {
|
||||
//normalize cname,mx,ns records with dots to be consistent with our config format.
|
||||
if c.Type == "CNAME" || c.Type == "MX" || c.Type == "NS" {
|
||||
c.Content = dnsutil.AddOrigin(c.Content+".", domain)
|
||||
}
|
||||
proxy := ""
|
||||
if c.Type == "A" || c.Type == "CNAME" || c.Type == "AAAA" {
|
||||
proxy = fmt.Sprintf(" proxy=%v ", c.Proxied)
|
||||
return &models.RecordConfig{
|
||||
NameFQDN: c.Name,
|
||||
Type: c.Type,
|
||||
Target: c.Content,
|
||||
Priority: c.Priority,
|
||||
TTL: c.TTL,
|
||||
Original: c,
|
||||
}
|
||||
return fmt.Sprintf("%d%s%s", c.TTL, mxPrio, proxy)
|
||||
}
|
||||
|
||||
// Used on the "expected" records.
|
||||
type recordWrapper struct {
|
||||
*models.RecordConfig
|
||||
}
|
||||
|
||||
func (c recordWrapper) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if c.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %d ", c.Priority)
|
||||
}
|
||||
proxy := ""
|
||||
if c.Type == "A" || c.Type == "AAAA" || c.Type == "CNAME" {
|
||||
proxy = fmt.Sprintf(" proxy=%v ", c.Metadata[metaProxy] != "off")
|
||||
}
|
||||
|
||||
ttl := c.TTL
|
||||
if ttl == 0 {
|
||||
ttl = 1
|
||||
}
|
||||
return fmt.Sprintf("%d%s%s", ttl, mxPrio, proxy)
|
||||
func getProxyMetadata(r *models.RecordConfig) map[string]string {
|
||||
if r.Type != "A" && r.Type != "AAAA" && r.Type != "CNAME" {
|
||||
return nil
|
||||
}
|
||||
proxied := false
|
||||
if r.Original != nil {
|
||||
proxied = r.Original.(*cfRecord).Proxied
|
||||
} else {
|
||||
proxied = r.Metadata[metaProxy] != "off"
|
||||
}
|
||||
return map[string]string{
|
||||
"proxy": fmt.Sprint(proxied),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ func TestIpRewriting(t *testing.T) {
|
|||
}
|
||||
cf := &CloudflareApi{}
|
||||
domain := newDomainConfig()
|
||||
cf.ipConversions = []transform.IpConversion{{net.ParseIP("1.2.3.0"), net.ParseIP("1.2.3.40"), net.ParseIP("255.255.255.0"), nil}}
|
||||
cf.ipConversions = []transform.IpConversion{{net.ParseIP("1.2.3.0"), net.ParseIP("1.2.3.40"), []net.IP{net.ParseIP("255.255.255.0")}, nil}}
|
||||
for _, tst := range tests {
|
||||
rec := &models.RecordConfig{Type: "A", Target: tst.Given, Metadata: map[string]string{metaProxy: tst.Proxy}}
|
||||
domain.Records = append(domain.Records, rec)
|
||||
|
@ -110,7 +110,3 @@ func TestIpRewriting(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCnameValidation(t *testing.T) {
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,10 +46,10 @@ func (c *CloudflareApi) fetchDomainList() error {
|
|||
}
|
||||
|
||||
// get all records for a domain
|
||||
func (c *CloudflareApi) getRecordsForDomain(id string) ([]diff.Record, error) {
|
||||
func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models.RecordConfig, error) {
|
||||
url := fmt.Sprintf(recordsURL, id)
|
||||
page := 1
|
||||
records := []diff.Record{}
|
||||
records := []*models.RecordConfig{}
|
||||
for {
|
||||
reqURL := fmt.Sprintf("%s?page=%d&per_page=100", url, page)
|
||||
var data recordsResponse
|
||||
|
@ -61,7 +60,7 @@ func (c *CloudflareApi) getRecordsForDomain(id string) ([]diff.Record, error) {
|
|||
return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors))
|
||||
}
|
||||
for _, rec := range data.Result {
|
||||
records = append(records, rec)
|
||||
records = append(records, rec.toRecord(domain))
|
||||
}
|
||||
ri := data.ResultInfo
|
||||
if len(data.Result) == 0 || ri.Page*ri.PerPage >= ri.TotalCount {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,58 +1,52 @@
|
|||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
)
|
||||
|
||||
type myRecord string //@ A 1 1.2.3.4
|
||||
|
||||
func (m myRecord) GetName() string {
|
||||
name := strings.SplitN(string(m), " ", 4)[0]
|
||||
return dnsutil.AddOrigin(name, "example.com")
|
||||
}
|
||||
func (m myRecord) GetType() string {
|
||||
return strings.SplitN(string(m), " ", 4)[1]
|
||||
}
|
||||
func (m myRecord) GetContent() string {
|
||||
return strings.SplitN(string(m), " ", 4)[3]
|
||||
}
|
||||
func (m myRecord) GetComparisionData() string {
|
||||
return fmt.Sprint(strings.SplitN(string(m), " ", 4)[2])
|
||||
func myRecord(s string) *models.RecordConfig {
|
||||
parts := strings.Split(s, " ")
|
||||
ttl, _ := strconv.ParseUint(parts[2], 10, 32)
|
||||
return &models.RecordConfig{
|
||||
NameFQDN: dnsutil.AddOrigin(parts[0], "example.com"),
|
||||
Type: parts[1],
|
||||
TTL: uint32(ttl),
|
||||
Target: parts[3],
|
||||
Metadata: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdditionsOnly(t *testing.T) {
|
||||
desired := []Record{
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("@ A 1 1.2.3.4"),
|
||||
}
|
||||
existing := []Record{}
|
||||
existing := []*models.RecordConfig{}
|
||||
checkLengths(t, existing, desired, 0, 1, 0, 0)
|
||||
}
|
||||
|
||||
func TestDeletionsOnly(t *testing.T) {
|
||||
existing := []Record{
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("@ A 1 1.2.3.4"),
|
||||
}
|
||||
desired := []Record{}
|
||||
desired := []*models.RecordConfig{}
|
||||
checkLengths(t, existing, desired, 0, 0, 1, 0)
|
||||
}
|
||||
|
||||
func TestModification(t *testing.T) {
|
||||
existing := []Record{
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
myRecord("@ A 1 1.2.3.4"),
|
||||
}
|
||||
desired := []Record{
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("@ A 32 1.2.3.4"),
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
}
|
||||
un, _, _, mod := checkLengths(t, existing, desired, 1, 0, 0, 1)
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
if un[0].Desired != desired[1] || un[0].Existing != existing[0] {
|
||||
t.Error("Expected unchanged records to be correlated")
|
||||
}
|
||||
|
@ -62,10 +56,10 @@ func TestModification(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUnchangedWithAddition(t *testing.T) {
|
||||
existing := []Record{
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
}
|
||||
desired := []Record{
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www A 1 1.2.3.4"),
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
}
|
||||
|
@ -76,12 +70,12 @@ func TestUnchangedWithAddition(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOutOfOrderRecords(t *testing.T) {
|
||||
existing := []Record{
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
myRecord("www A 1 2.2.2.2"),
|
||||
myRecord("www A 1 3.3.3.3"),
|
||||
}
|
||||
desired := []Record{
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www A 1 1.1.1.1"),
|
||||
myRecord("www A 1 2.2.2.2"),
|
||||
myRecord("www A 1 2.2.2.3"),
|
||||
|
@ -91,11 +85,55 @@ func TestOutOfOrderRecords(t *testing.T) {
|
|||
if mods[0].Desired != desired[3] || mods[0].Existing != existing[2] {
|
||||
t.Fatalf("Expected to match %s and %s, but matched %s and %s", existing[2], desired[3], mods[0].Existing, mods[0].Desired)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkLengths(t *testing.T, existing, desired []Record, unCount, createCount, delCount, modCount int) (un, cre, del, mod Changeset) {
|
||||
un, cre, del, mod = IncrementalDiff(existing, desired)
|
||||
func TestMxPrio(t *testing.T) {
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
existing[0].Priority = 10
|
||||
desired[0].Priority = 20
|
||||
checkLengths(t, existing, desired, 0, 0, 0, 1)
|
||||
}
|
||||
|
||||
func TestTTLChange(t *testing.T) {
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www MX 10 1.1.1.1"),
|
||||
}
|
||||
checkLengths(t, existing, desired, 0, 0, 0, 1)
|
||||
}
|
||||
|
||||
func TestMetaChange(t *testing.T) {
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
existing[0].Metadata["k"] = "aa"
|
||||
desired[0].Metadata["k"] = "bb"
|
||||
checkLengths(t, existing, desired, 1, 0, 0, 0)
|
||||
getMeta := func(r *models.RecordConfig) map[string]string {
|
||||
return map[string]string{
|
||||
"k": r.Metadata["k"],
|
||||
}
|
||||
}
|
||||
checkLengths(t, existing, desired, 0, 0, 0, 1, getMeta)
|
||||
}
|
||||
|
||||
func checkLengths(t *testing.T, existing, desired []*models.RecordConfig, unCount, createCount, delCount, modCount int, valFuncs ...func(*models.RecordConfig) map[string]string) (un, cre, del, mod Changeset) {
|
||||
dc := &models.DomainConfig{
|
||||
Name: "example.com",
|
||||
Records: desired,
|
||||
}
|
||||
d := New(dc, valFuncs...)
|
||||
un, cre, del, mod = d.IncrementalDiff(existing)
|
||||
if len(un) != unCount {
|
||||
t.Errorf("Got %d unchanged records, but expected %d", len(un), unCount)
|
||||
}
|
||||
|
@ -108,5 +146,8 @@ func checkLengths(t *testing.T, existing, desired []Record, unCount, createCount
|
|||
if len(mod) != modCount {
|
||||
t.Errorf("Got %d records to modify, but expected %d", len(mod), modCount)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package gandi
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
|
@ -30,49 +28,10 @@ type GandiApi struct {
|
|||
ZoneId int64
|
||||
}
|
||||
|
||||
type cfRecord struct {
|
||||
type gandiRecord struct {
|
||||
gandirecord.RecordInfo
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetType() string {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetTtl() int64 {
|
||||
return c.Ttl
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetValue() string {
|
||||
return c.Value
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetContent() string {
|
||||
switch c.Type {
|
||||
case "MX":
|
||||
parts := strings.SplitN(c.Value, " ", 2)
|
||||
// TODO(tlim): This should check for more errors.
|
||||
return strings.Join(parts[1:], " ")
|
||||
default:
|
||||
}
|
||||
return c.Value
|
||||
}
|
||||
|
||||
func (c *cfRecord) GetComparisionData() string {
|
||||
if c.Type == "MX" {
|
||||
parts := strings.SplitN(c.Value, " ", 2)
|
||||
priority, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%s %#v", c.Ttl, parts[0])
|
||||
}
|
||||
return fmt.Sprintf("%d %d", c.Ttl, priority)
|
||||
}
|
||||
return fmt.Sprintf("%d", c.Ttl)
|
||||
}
|
||||
|
||||
func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) {
|
||||
if err := c.fetchDomainList(); err != nil {
|
||||
return nil, err
|
||||
|
@ -95,6 +54,7 @@ func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
|||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
domaininfo, err := c.getDomainInfo(dc.Name)
|
||||
if err != nil {
|
||||
|
@ -104,46 +64,23 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert to []diff.Records and compare:
|
||||
foundDiffRecords := make([]diff.Record, len(foundRecords))
|
||||
for i, rec := range foundRecords {
|
||||
n := &cfRecord{}
|
||||
n.Id = 0
|
||||
n.Name = rec.Name
|
||||
n.Ttl = int64(rec.Ttl)
|
||||
n.Type = rec.Type
|
||||
n.Value = rec.Value
|
||||
foundDiffRecords[i] = n
|
||||
}
|
||||
expectedDiffRecords := make([]diff.Record, len(dc.Records))
|
||||
|
||||
expectedRecordSets := make([]gandirecord.RecordSet, len(dc.Records))
|
||||
for i, rec := range dc.Records {
|
||||
n := &cfRecord{}
|
||||
n.Id = 0
|
||||
n.Name = rec.Name
|
||||
n.Ttl = int64(rec.TTL)
|
||||
if n.Ttl == 0 {
|
||||
n.Ttl = 3600
|
||||
if rec.Type == "MX" {
|
||||
rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target)
|
||||
}
|
||||
n.Type = rec.Type
|
||||
switch n.Type {
|
||||
case "MX":
|
||||
n.Value = fmt.Sprintf("%d %s", rec.Priority, rec.Target)
|
||||
case "TXT":
|
||||
n.Value = "\"" + rec.Target + "\"" // FIXME(tlim): Should do proper quoting.
|
||||
default:
|
||||
n.Value = rec.Target
|
||||
if rec.Type == "TXT" {
|
||||
rec.Target = "\"" + rec.Target + "\"" // FIXME(tlim): Should do proper quoting.
|
||||
}
|
||||
expectedDiffRecords[i] = n
|
||||
expectedRecordSets[i] = gandirecord.RecordSet{}
|
||||
expectedRecordSets[i]["type"] = n.Type
|
||||
expectedRecordSets[i]["name"] = n.Name
|
||||
expectedRecordSets[i]["value"] = n.Value
|
||||
if n.Ttl != 0 {
|
||||
expectedRecordSets[i]["ttl"] = n.Ttl
|
||||
}
|
||||
expectedRecordSets[i]["type"] = rec.Type
|
||||
expectedRecordSets[i]["name"] = rec.Name
|
||||
expectedRecordSets[i]["value"] = rec.Target
|
||||
expectedRecordSets[i]["ttl"] = rec.TTL
|
||||
}
|
||||
_, create, del, mod := diff.IncrementalDiff(foundDiffRecords, expectedDiffRecords)
|
||||
differ := diff.New(dc)
|
||||
_, create, del, mod := differ.IncrementalDiff(foundRecords)
|
||||
|
||||
// Print a list of changes. Generate an actual change that is the zone
|
||||
changes := false
|
||||
|
@ -160,7 +97,7 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
|
|||
fmt.Println(i)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)", dc.Name, len(expectedDiffRecords))
|
||||
msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)", dc.Name, len(dc.Records))
|
||||
corrections := []*models.Correction{}
|
||||
if changes {
|
||||
corrections = append(corrections,
|
||||
|
|
|
@ -3,15 +3,13 @@ package gandi
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
)
|
||||
|
||||
import (
|
||||
gandiclient "github.com/prasmussen/gandi-api/client"
|
||||
gandidomain "github.com/prasmussen/gandi-api/domain"
|
||||
gandizone "github.com/prasmussen/gandi-api/domain/zone"
|
||||
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
|
||||
gandiversion "github.com/prasmussen/gandi-api/domain/zone/version"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
// fetchDomainList gets list of domains for account. Cache ids for easy lookup.
|
||||
|
@ -41,10 +39,18 @@ func (c *GandiApi) fetchDomainInfo(fqdn string) (*gandidomain.DomainInfo, error)
|
|||
}
|
||||
|
||||
// getRecordsForDomain returns a list of records for a zone.
|
||||
func (c *GandiApi) getZoneRecords(zoneid int64) ([]*gandirecord.RecordInfo, error) {
|
||||
func (c *GandiApi) getZoneRecords(zoneid int64) ([]*models.RecordConfig, error) {
|
||||
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
|
||||
record := gandirecord.New(gc)
|
||||
return record.List(zoneid, 0)
|
||||
recs, err := record.List(zoneid, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rcs := make([]*models.RecordConfig, 0, len(recs))
|
||||
for _, r := range recs {
|
||||
rcs = append(rcs, convert(r))
|
||||
}
|
||||
return rcs, nil
|
||||
}
|
||||
|
||||
// listZones retrieves the list of zones.
|
||||
|
@ -75,11 +81,6 @@ func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
|
|||
return zone.Create(name)
|
||||
}
|
||||
|
||||
// replaceZoneContents
|
||||
func (c *GandiApi) replaceZoneContents(zone_id int64, version_id int64, records []diff.Record) error {
|
||||
return fmt.Errorf("replaceZoneContents unimplemented")
|
||||
}
|
||||
|
||||
func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
|
||||
var zone_id int64
|
||||
if zoneinfo.Domains < 2 {
|
||||
|
@ -169,3 +170,13 @@ func (c *GandiApi) createGandiZone(domainname string, zone_id int64, records []g
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert(r *gandirecord.RecordInfo) *models.RecordConfig {
|
||||
return &models.RecordConfig{
|
||||
NameFQDN: r.Name,
|
||||
Type: r.Type,
|
||||
Original: r,
|
||||
Target: r.Value,
|
||||
TTL: uint32(r.Ttl),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||
return nil, err
|
||||
}
|
||||
//convert to dnscontrol RecordConfig format
|
||||
existingRecords := []diff.Record{}
|
||||
existingRecords := []*models.RecordConfig{}
|
||||
oldRRs := map[key]*dns.ResourceRecordSet{}
|
||||
for _, set := range rrs {
|
||||
nameWithoutDot := set.Name
|
||||
|
@ -123,11 +123,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||
}
|
||||
}
|
||||
|
||||
w := []diff.Record{}
|
||||
for _, want := range dc.Records {
|
||||
if want.TTL == 0 {
|
||||
want.TTL = 300
|
||||
}
|
||||
if want.Type == "MX" {
|
||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
||||
want.Priority = 0
|
||||
|
@ -135,24 +131,24 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||
//add quotes to txts
|
||||
want.Target = fmt.Sprintf(`"%s"`, want.Target)
|
||||
}
|
||||
w = append(w, want)
|
||||
}
|
||||
|
||||
// first collect keys that have changed
|
||||
_, create, delete, modify := diff.IncrementalDiff(existingRecords, w)
|
||||
differ := diff.New(dc)
|
||||
_, create, delete, modify := differ.IncrementalDiff(existingRecords)
|
||||
changedKeys := map[key]bool{}
|
||||
desc := ""
|
||||
for _, c := range create {
|
||||
desc += fmt.Sprintln(c)
|
||||
changedKeys[keyForRec(c.Desired.(*models.RecordConfig))] = true
|
||||
changedKeys[keyForRec(c.Desired)] = true
|
||||
}
|
||||
for _, d := range delete {
|
||||
desc += fmt.Sprintln(d)
|
||||
changedKeys[keyForRec(d.Existing.(*models.RecordConfig))] = true
|
||||
changedKeys[keyForRec(d.Existing)] = true
|
||||
}
|
||||
for _, m := range modify {
|
||||
desc += fmt.Sprintln(m)
|
||||
changedKeys[keyForRec(m.Existing.(*models.RecordConfig))] = true
|
||||
changedKeys[keyForRec(m.Existing)] = true
|
||||
}
|
||||
if len(changedKeys) == 0 {
|
||||
return nil, nil
|
||||
|
|
|
@ -175,7 +175,11 @@ func TestGetNameservers(t *testing.T) {
|
|||
t.Errorf("Test %d: %s", i, err)
|
||||
continue
|
||||
}
|
||||
if strings.Join(found, ",") != test.expected {
|
||||
fStrs := []string{}
|
||||
for _, n := range found {
|
||||
fStrs = append(fStrs, n.Name)
|
||||
}
|
||||
if strings.Join(fStrs, ",") != test.expected {
|
||||
t.Errorf("Test %d: Expected '%s', but found '%s'", i, test.expected, found)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var defaultNameservers = []*models.Nameserver{
|
||||
|
@ -23,42 +24,30 @@ func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actual := make([]diff.Record, len(records))
|
||||
for i := range records {
|
||||
actual[i] = records[i]
|
||||
actual := make([]*models.RecordConfig, len(records))
|
||||
for i, r := range records {
|
||||
actual[i] = r.toRecord()
|
||||
}
|
||||
|
||||
desired := make([]diff.Record, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = 300
|
||||
}
|
||||
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||
// name.com does change base domain NS records. dnscontrol will print warnings if you try to set them to anything besides the name.com defaults.
|
||||
if !strings.HasSuffix(rec.Target, ".name.com.") {
|
||||
log.Printf("Warning: name.com does not allow NS records on base domain to be modified. %s will not be added.", rec.Target)
|
||||
}
|
||||
continue
|
||||
}
|
||||
desired = append(desired, rec)
|
||||
}
|
||||
checkNSModifications(dc)
|
||||
|
||||
_, create, del, mod := diff.IncrementalDiff(actual, desired)
|
||||
differ := diff.New(dc)
|
||||
_, create, del, mod := differ.IncrementalDiff(actual)
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, d := range del {
|
||||
rec := d.Existing.(*nameComRecord)
|
||||
rec := d.Existing.Original.(*nameComRecord)
|
||||
c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.RecordID, dc.Name) }}
|
||||
corrections = append(corrections, c)
|
||||
}
|
||||
for _, cre := range create {
|
||||
rec := cre.Desired.(*models.RecordConfig)
|
||||
rec := cre.Desired.Original.(*models.RecordConfig)
|
||||
c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
|
||||
corrections = append(corrections, c)
|
||||
}
|
||||
for _, chng := range mod {
|
||||
old := chng.Existing.(*nameComRecord)
|
||||
new := chng.Desired.(*models.RecordConfig)
|
||||
old := chng.Existing.Original.(*nameComRecord)
|
||||
new := chng.Desired
|
||||
c := &models.Correction{Msg: chng.String(), F: func() error {
|
||||
err := n.deleteRecord(old.RecordID, dc.Name)
|
||||
if err != nil {
|
||||
|
@ -90,21 +79,32 @@ type nameComRecord struct {
|
|||
Priority string `json:"priority"`
|
||||
}
|
||||
|
||||
func (r *nameComRecord) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
func (r *nameComRecord) GetType() string {
|
||||
return r.Type
|
||||
}
|
||||
func (r *nameComRecord) GetContent() string {
|
||||
return r.Content
|
||||
}
|
||||
func (r *nameComRecord) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if r.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %s ", r.Priority)
|
||||
func checkNSModifications(dc *models.DomainConfig) {
|
||||
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||
for _, rec := range dc.Records {
|
||||
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||
// name.com does change base domain NS records. dnscontrol will print warnings if you try to set them to anything besides the name.com defaults.
|
||||
if !strings.HasSuffix(rec.Target, ".name.com.") {
|
||||
log.Printf("Warning: name.com does not allow NS records on base domain to be modified. %s will not be added.", rec.Target)
|
||||
}
|
||||
continue
|
||||
}
|
||||
newList = append(newList, rec)
|
||||
}
|
||||
dc.Records = newList
|
||||
}
|
||||
|
||||
func (r *nameComRecord) toRecord() *models.RecordConfig {
|
||||
ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
|
||||
prio, _ := strconv.ParseUint(r.Priority, 10, 16)
|
||||
return &models.RecordConfig{
|
||||
NameFQDN: r.Name,
|
||||
Type: r.Type,
|
||||
Target: r.Content,
|
||||
TTL: uint32(ttl),
|
||||
Priority: uint16(prio),
|
||||
Original: r,
|
||||
}
|
||||
return fmt.Sprintf("%s%s", r.TTL, mxPrio)
|
||||
}
|
||||
|
||||
type listRecordsResponse struct {
|
||||
|
|
|
@ -40,6 +40,7 @@ func init() {
|
|||
func sPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func (r *route53Provider) getZones() error {
|
||||
if r.zones != nil {
|
||||
return nil
|
||||
|
@ -73,8 +74,8 @@ type key struct {
|
|||
Name, Type string
|
||||
}
|
||||
|
||||
func getKey(r diff.Record) key {
|
||||
return key{r.GetName(), r.GetType()}
|
||||
func getKey(r *models.RecordConfig) key {
|
||||
return key{r.NameFQDN, r.Type}
|
||||
}
|
||||
|
||||
type errNoExist struct {
|
||||
|
@ -121,8 +122,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
return nil, err
|
||||
}
|
||||
|
||||
//convert to dnscontrol RecordConfig format
|
||||
var existingRecords = []diff.Record{}
|
||||
var existingRecords = []*models.RecordConfig{}
|
||||
for _, set := range records {
|
||||
for _, rec := range set.ResourceRecords {
|
||||
if *set.Type == "SOA" {
|
||||
|
@ -137,23 +137,19 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
existingRecords = append(existingRecords, r)
|
||||
}
|
||||
}
|
||||
w := []diff.Record{}
|
||||
for _, want := range dc.Records {
|
||||
if want.TTL == 0 {
|
||||
want.TTL = 300
|
||||
}
|
||||
if want.Type == "MX" {
|
||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
||||
want.Priority = 0
|
||||
} else if want.Type == "TXT" {
|
||||
want.Target = fmt.Sprintf(`"%s"`, want.Target) //FIXME: better escaping/quoting
|
||||
}
|
||||
w = append(w, want)
|
||||
}
|
||||
|
||||
//diff
|
||||
changeDesc := ""
|
||||
_, create, delete, modify := diff.IncrementalDiff(existingRecords, w)
|
||||
differ := diff.New(dc)
|
||||
_, create, delete, modify := differ.IncrementalDiff(existingRecords)
|
||||
|
||||
namesToUpdate := map[key]bool{}
|
||||
for _, c := range create {
|
||||
|
|
Loading…
Add table
Reference in a new issue