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:
@ -66,23 +66,19 @@ type RecordConfig struct {
|
|||||||
Metadata map[string]string `json:"meta,omitempty"`
|
Metadata map[string]string `json:"meta,omitempty"`
|
||||||
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
||||||
Priority uint16 `json:"priority,omitempty"`
|
Priority uint16 `json:"priority,omitempty"`
|
||||||
|
|
||||||
|
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordConfig) GetName() string {
|
func (r *RecordConfig) String() string {
|
||||||
return r.NameFQDN
|
content := fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL)
|
||||||
}
|
|
||||||
func (r *RecordConfig) GetType() string {
|
|
||||||
return r.Type
|
|
||||||
}
|
|
||||||
func (r *RecordConfig) GetContent() string {
|
|
||||||
return r.Target
|
|
||||||
}
|
|
||||||
func (r *RecordConfig) GetComparisionData() string {
|
|
||||||
mxPrio := ""
|
|
||||||
if r.Type == "MX" {
|
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.
|
/// Convert RecordConfig -> dns.RR.
|
||||||
|
@ -200,6 +200,9 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
|||||||
|
|
||||||
// Normalize Records.
|
// Normalize Records.
|
||||||
for _, rec := range domain.Records {
|
for _, rec := range domain.Records {
|
||||||
|
if rec.TTL == 0 {
|
||||||
|
rec.TTL = models.DefaultTTL
|
||||||
|
}
|
||||||
// Validate the unmodified inputs:
|
// Validate the unmodified inputs:
|
||||||
if err := validateRecordTypes(rec, domain.Name); err != nil {
|
if err := validateRecordTypes(rec, domain.Name); err != nil {
|
||||||
errs = append(errs, err)
|
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)
|
return nil, fmt.Errorf("c.getExistingRecords(%v) failed: %v", dc.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read expectedRecords:
|
differ := diff.New(dc)
|
||||||
//expectedRecords := make([]*models.RecordConfig, len(dc.Records))
|
_, creates, dels, modifications := differ.IncrementalDiff(foundRecords)
|
||||||
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)
|
|
||||||
// NOTE(tlim): This provider does not delete records. If
|
// NOTE(tlim): This provider does not delete records. If
|
||||||
// you need to delete a record, either delete it manually
|
// you need to delete a record, either delete it manually
|
||||||
// or see providers/activedir/doc.md for implementation tips.
|
// 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 {
|
if dc.KeepUnknown {
|
||||||
break
|
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 {
|
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 {
|
for _, m := range modifications {
|
||||||
corrections = append(corrections, c.modifyRec(dc.Name, m))
|
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 {
|
func (c *adProvider) modifyRec(domainname string, m diff.Correlation) *models.Correction {
|
||||||
|
old, rec := m.Existing, m.Desired
|
||||||
old, rec := m.Existing.(*models.RecordConfig), m.Desired.(*models.RecordConfig)
|
|
||||||
oldContent := old.GetContent()
|
|
||||||
newContent := rec.GetContent()
|
|
||||||
|
|
||||||
return &models.Correction{
|
return &models.Correction{
|
||||||
Msg: m.String(),
|
Msg: m.String(),
|
||||||
F: func() error {
|
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.
|
// Default SOA record. If we see one in the zone, this will be replaced.
|
||||||
soa_rec := makeDefaultSOA(c.Default_Soa, dc.Name)
|
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:
|
// Read foundRecords:
|
||||||
foundRecords := make([]*models.RecordConfig, 0)
|
foundRecords := make([]*models.RecordConfig, 0)
|
||||||
var old_serial, new_serial uint32
|
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", "@") {
|
if !dc.HasRecordTypeName("SOA", "@") {
|
||||||
expectedRecords = append(expectedRecords, soa_rec)
|
|
||||||
dc.Records = append(dc.Records, soa_rec)
|
dc.Records = append(dc.Records, soa_rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to []diff.Records and compare:
|
differ := diff.New(dc)
|
||||||
foundDiffRecords := make([]diff.Record, len(foundRecords))
|
_, create, del, mod := differ.IncrementalDiff(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)
|
|
||||||
|
|
||||||
// Print a list of changes. Generate an actual change that is the zone
|
// Print a list of changes. Generate an actual change that is the zone
|
||||||
changes := false
|
changes := false
|
||||||
|
@ -31,7 +31,6 @@ Domain level metadata availible:
|
|||||||
|
|
||||||
Provider level metadata availible:
|
Provider level metadata availible:
|
||||||
- ip_conversions
|
- ip_conversions
|
||||||
- secret_ips
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type CloudflareApi struct {
|
type CloudflareApi struct {
|
||||||
@ -40,7 +39,6 @@ type CloudflareApi struct {
|
|||||||
domainIndex map[string]string
|
domainIndex map[string]string
|
||||||
nameservers map[string][]string
|
nameservers map[string][]string
|
||||||
ipConversions []transform.IpConversion
|
ipConversions []transform.IpConversion
|
||||||
secretIPs []net.IP
|
|
||||||
ignoredLabels []string
|
ignoredLabels []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +77,7 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||||||
if err := c.preprocessConfig(dc); err != nil {
|
if err := c.preprocessConfig(dc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
records, err := c.getRecordsForDomain(id)
|
records, err := c.getRecordsForDomain(id, dc.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -87,50 +85,52 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||||||
for i := len(records) - 1; i >= 0; i-- {
|
for i := len(records) - 1; i >= 0; i-- {
|
||||||
rec := records[i]
|
rec := records[i]
|
||||||
// Delete ignore labels
|
// Delete ignore labels
|
||||||
if labelMatches(dnsutil.TrimDomainName(rec.(*cfRecord).Name, dc.Name), c.ignoredLabels) {
|
if labelMatches(dnsutil.TrimDomainName(rec.Original.(*cfRecord).Name, dc.Name), c.ignoredLabels) {
|
||||||
fmt.Printf("ignored_label: %s\n", rec.(*cfRecord).Name)
|
fmt.Printf("ignored_label: %s\n", rec.Original.(*cfRecord).Name)
|
||||||
records = append(records[:i], records[i+1:]...)
|
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 {
|
for _, rec := range dc.Records {
|
||||||
if labelMatches(rec.Name, c.ignoredLabels) {
|
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)
|
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.
|
// 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 rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||||
if !strings.HasSuffix(rec.Target, ".ns.cloudflare.com.") {
|
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)
|
log.Printf("Warning: cloudflare does not support modifying NS records on base domain. %s will not be added.", rec.Target)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
expectedRecords = append(expectedRecords, recordWrapper{rec})
|
newList = append(newList, rec)
|
||||||
}
|
}
|
||||||
_, create, del, mod := diff.IncrementalDiff(records, expectedRecords)
|
dc.Records = newList
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -138,7 +138,6 @@ const (
|
|||||||
metaProxyDefault = metaProxy + "_default"
|
metaProxyDefault = metaProxy + "_default"
|
||||||
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
|
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
|
||||||
metaIPConversions = "ip_conversions" // TODO(tlim): Rename to obscure_rules.
|
metaIPConversions = "ip_conversions" // TODO(tlim): Rename to obscure_rules.
|
||||||
metaSecretIPs = "secret_ips" // TODO(tlim): Rename to obscured_cidrs.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkProxyVal(v string) (string, error) {
|
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.
|
// A and CNAMEs: Validate. If null, set to default.
|
||||||
// else: Make sure it wasn't set. Set to default.
|
// else: Make sure it wasn't set. Set to default.
|
||||||
for _, rec := range dc.Records {
|
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.Type != "A" && rec.Type != "CNAME" && rec.Type != "AAAA" {
|
||||||
if rec.Metadata[metaProxy] != "" {
|
if rec.Metadata[metaProxy] != "" {
|
||||||
return fmt.Errorf("cloudflare_proxy set on %v record: %#v cloudflare_proxy=%#v", rec.Type, rec.Name, 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
|
// look for ip conversions and transform records
|
||||||
for _, rec := range dc.Records {
|
for _, rec := range dc.Records {
|
||||||
if rec.TTL == 0 {
|
|
||||||
rec.TTL = 1
|
|
||||||
}
|
|
||||||
if rec.Type != "A" {
|
if rec.Type != "A" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -223,9 +222,8 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||||||
|
|
||||||
if len(metadata) > 0 {
|
if len(metadata) > 0 {
|
||||||
parsedMeta := &struct {
|
parsedMeta := &struct {
|
||||||
IPConversions string `json:"ip_conversions"`
|
IPConversions string `json:"ip_conversions"`
|
||||||
SecretIps []interface{} `json:"secret_ips"`
|
IgnoredLabels []string `json:"ignored_labels"`
|
||||||
IgnoredLabels []string `json:"ignored_labels"`
|
|
||||||
}{}
|
}{}
|
||||||
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -240,15 +238,6 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return api, nil
|
||||||
}
|
}
|
||||||
@ -265,58 +254,42 @@ type cfRecord struct {
|
|||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Proxiable bool `json:"proxiable"`
|
Proxiable bool `json:"proxiable"`
|
||||||
Proxied bool `json:"proxied"`
|
Proxied bool `json:"proxied"`
|
||||||
TTL int `json:"ttl"`
|
TTL uint32 `json:"ttl"`
|
||||||
Locked bool `json:"locked"`
|
Locked bool `json:"locked"`
|
||||||
ZoneID string `json:"zone_id"`
|
ZoneID string `json:"zone_id"`
|
||||||
ZoneName string `json:"zone_name"`
|
ZoneName string `json:"zone_name"`
|
||||||
CreatedOn time.Time `json:"created_on"`
|
CreatedOn time.Time `json:"created_on"`
|
||||||
ModifiedOn time.Time `json:"modified_on"`
|
ModifiedOn time.Time `json:"modified_on"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Priority int `json:"priority"`
|
Priority uint16 `json:"priority"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cfRecord) GetName() string {
|
func (c *cfRecord) toRecord(domain string) *models.RecordConfig {
|
||||||
return c.Name
|
//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)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
proxy := ""
|
return &models.RecordConfig{
|
||||||
if c.Type == "A" || c.Type == "CNAME" || c.Type == "AAAA" {
|
NameFQDN: c.Name,
|
||||||
proxy = fmt.Sprintf(" proxy=%v ", c.Proxied)
|
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.
|
func getProxyMetadata(r *models.RecordConfig) map[string]string {
|
||||||
type recordWrapper struct {
|
if r.Type != "A" && r.Type != "AAAA" && r.Type != "CNAME" {
|
||||||
*models.RecordConfig
|
return nil
|
||||||
}
|
}
|
||||||
|
proxied := false
|
||||||
func (c recordWrapper) GetComparisionData() string {
|
if r.Original != nil {
|
||||||
mxPrio := ""
|
proxied = r.Original.(*cfRecord).Proxied
|
||||||
if c.Type == "MX" {
|
} else {
|
||||||
mxPrio = fmt.Sprintf(" %d ", c.Priority)
|
proxied = r.Metadata[metaProxy] != "off"
|
||||||
}
|
}
|
||||||
proxy := ""
|
return map[string]string{
|
||||||
if c.Type == "A" || c.Type == "AAAA" || c.Type == "CNAME" {
|
"proxy": fmt.Sprint(proxied),
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func TestIpRewriting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
cf := &CloudflareApi{}
|
cf := &CloudflareApi{}
|
||||||
domain := newDomainConfig()
|
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 {
|
for _, tst := range tests {
|
||||||
rec := &models.RecordConfig{Type: "A", Target: tst.Given, Metadata: map[string]string{metaProxy: tst.Proxy}}
|
rec := &models.RecordConfig{Type: "A", Target: tst.Given, Metadata: map[string]string{metaProxy: tst.Proxy}}
|
||||||
domain.Records = append(domain.Records, rec)
|
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"
|
"net/http"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -47,10 +46,10 @@ func (c *CloudflareApi) fetchDomainList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all records for a domain
|
// 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)
|
url := fmt.Sprintf(recordsURL, id)
|
||||||
page := 1
|
page := 1
|
||||||
records := []diff.Record{}
|
records := []*models.RecordConfig{}
|
||||||
for {
|
for {
|
||||||
reqURL := fmt.Sprintf("%s?page=%d&per_page=100", url, page)
|
reqURL := fmt.Sprintf("%s?page=%d&per_page=100", url, page)
|
||||||
var data recordsResponse
|
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))
|
return nil, fmt.Errorf("Error fetching record list cloudflare: %s", stringifyErrors(data.Errors))
|
||||||
}
|
}
|
||||||
for _, rec := range data.Result {
|
for _, rec := range data.Result {
|
||||||
records = append(records, rec)
|
records = append(records, rec.toRecord(domain))
|
||||||
}
|
}
|
||||||
ri := data.ResultInfo
|
ri := data.ResultInfo
|
||||||
if len(data.Result) == 0 || ri.Page*ri.PerPage >= ri.TotalCount {
|
if len(data.Result) == 0 || ri.Page*ri.PerPage >= ri.TotalCount {
|
||||||
|
@ -3,69 +3,83 @@ package diff
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"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 {
|
type Correlation struct {
|
||||||
Existing Record
|
d *differ
|
||||||
Desired Record
|
Existing *models.RecordConfig
|
||||||
|
Desired *models.RecordConfig
|
||||||
}
|
}
|
||||||
type Changeset []Correlation
|
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{}
|
unchanged = Changeset{}
|
||||||
create = Changeset{}
|
create = Changeset{}
|
||||||
toDelete = Changeset{}
|
toDelete = Changeset{}
|
||||||
modify = Changeset{}
|
modify = Changeset{}
|
||||||
|
desired := d.dc.Records
|
||||||
// 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)
|
|
||||||
// }
|
|
||||||
|
|
||||||
//sort existing and desired by name
|
//sort existing and desired by name
|
||||||
type key struct {
|
type key struct {
|
||||||
name, rType string
|
name, rType string
|
||||||
}
|
}
|
||||||
existingByNameAndType := map[key][]Record{}
|
existingByNameAndType := map[key][]*models.RecordConfig{}
|
||||||
desiredByNameAndType := map[key][]Record{}
|
desiredByNameAndType := map[key][]*models.RecordConfig{}
|
||||||
for _, e := range existing {
|
for _, e := range existing {
|
||||||
k := key{e.GetName(), e.GetType()}
|
k := key{e.NameFQDN, e.Type}
|
||||||
existingByNameAndType[k] = append(existingByNameAndType[k], e)
|
existingByNameAndType[k] = append(existingByNameAndType[k], e)
|
||||||
}
|
}
|
||||||
for _, d := range desired {
|
for _, d := range desired {
|
||||||
k := key{d.GetName(), d.GetType()}
|
k := key{d.NameFQDN, d.Type}
|
||||||
desiredByNameAndType[k] = append(desiredByNameAndType[k], d)
|
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 {
|
for key, existingRecords := range existingByNameAndType {
|
||||||
desiredRecords := desiredByNameAndType[key]
|
desiredRecords := desiredByNameAndType[key]
|
||||||
|
//first look through records that are the same target on both sides. Those are either modifications or unchanged
|
||||||
//first look through records that are the same content on both sides. Those are either modifications or unchanged
|
|
||||||
|
|
||||||
for i := len(existingRecords) - 1; i >= 0; i-- {
|
for i := len(existingRecords) - 1; i >= 0; i-- {
|
||||||
ex := existingRecords[i]
|
ex := existingRecords[i]
|
||||||
for j, de := range desiredRecords {
|
for j, de := range desiredRecords {
|
||||||
if de.GetContent() == ex.GetContent() {
|
if de.Target == ex.Target {
|
||||||
//they're either identical or should be a modification of each other
|
//they're either identical or should be a modification of each other (ttl or metadata changes)
|
||||||
if de.GetComparisionData() == ex.GetComparisionData() {
|
if d.content(de) == d.content(ex) {
|
||||||
unchanged = append(unchanged, Correlation{ex, de})
|
unchanged = append(unchanged, Correlation{d, ex, de})
|
||||||
} else {
|
} else {
|
||||||
modify = append(modify, Correlation{ex, de})
|
modify = append(modify, Correlation{d, ex, de})
|
||||||
}
|
}
|
||||||
// remove from both slices by index
|
// remove from both slices by index
|
||||||
existingRecords = existingRecords[:i+copy(existingRecords[i:], existingRecords[i+1:])]
|
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{}
|
desiredLookup := map[string]*models.RecordConfig{}
|
||||||
existingLookup := map[string]Record{}
|
existingLookup := map[string]*models.RecordConfig{}
|
||||||
// build index based on normalized value/ttl
|
// build index based on normalized content data
|
||||||
for _, ex := range existingRecords {
|
for _, ex := range existingRecords {
|
||||||
normalized := fmt.Sprintf("%s %s", ex.GetContent(), ex.GetComparisionData())
|
normalized := d.content(ex)
|
||||||
if existingLookup[normalized] != nil {
|
if existingLookup[normalized] != nil {
|
||||||
panic(fmt.Sprintf("DUPLICATE E_RECORD FOUND: %s %s", key, normalized))
|
panic(fmt.Sprintf("DUPLICATE E_RECORD FOUND: %s %s", key, normalized))
|
||||||
}
|
}
|
||||||
existingLookup[normalized] = ex
|
existingLookup[normalized] = ex
|
||||||
}
|
}
|
||||||
for _, de := range desiredRecords {
|
for _, de := range desiredRecords {
|
||||||
normalized := fmt.Sprintf("%s %s", de.GetContent(), de.GetComparisionData())
|
normalized := d.content(de)
|
||||||
if desiredLookup[normalized] != nil {
|
if desiredLookup[normalized] != nil {
|
||||||
panic(fmt.Sprintf("DUPLICATE D_RECORD FOUND: %s %s", key, normalized))
|
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
|
// if a record is in both, it is unchanged
|
||||||
for norm, ex := range existingLookup {
|
for norm, ex := range existingLookup {
|
||||||
if de, ok := desiredLookup[norm]; ok {
|
if de, ok := desiredLookup[norm]; ok {
|
||||||
unchanged = append(unchanged, Correlation{ex, de})
|
unchanged = append(unchanged, Correlation{d, ex, de})
|
||||||
delete(existingLookup, norm)
|
delete(existingLookup, norm)
|
||||||
delete(desiredLookup, norm)
|
delete(desiredLookup, norm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//sort records by normalized text. Keeps behaviour deterministic
|
//sort records by normalized text. Keeps behaviour deterministic
|
||||||
existingStrings, desiredStrings := []string{}, []string{}
|
existingStrings, desiredStrings := sortedKeys(existingLookup), sortedKeys(desiredLookup)
|
||||||
for norm := range existingLookup {
|
|
||||||
existingStrings = append(existingStrings, norm)
|
|
||||||
}
|
|
||||||
for norm := range desiredLookup {
|
|
||||||
desiredStrings = append(desiredStrings, norm)
|
|
||||||
}
|
|
||||||
sort.Strings(existingStrings)
|
|
||||||
sort.Strings(desiredStrings)
|
|
||||||
// Modifications. Take 1 from each side.
|
// Modifications. Take 1 from each side.
|
||||||
for len(desiredStrings) > 0 && len(existingStrings) > 0 {
|
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:]
|
existingStrings = existingStrings[1:]
|
||||||
desiredStrings = desiredStrings[1:]
|
desiredStrings = desiredStrings[1:]
|
||||||
}
|
}
|
||||||
// If desired still has things they are additions
|
// If desired still has things they are additions
|
||||||
for _, norm := range desiredStrings {
|
for _, norm := range desiredStrings {
|
||||||
rec := desiredLookup[norm]
|
rec := desiredLookup[norm]
|
||||||
create = append(create, Correlation{nil, rec})
|
create = append(create, Correlation{d, nil, rec})
|
||||||
}
|
}
|
||||||
// if found , but not desired, delete it
|
// if found , but not desired, delete it
|
||||||
for _, norm := range existingStrings {
|
for _, norm := range existingStrings {
|
||||||
rec := existingLookup[norm]
|
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.
|
// remove this set from the desired list to indicate we have processed it.
|
||||||
delete(desiredByNameAndType, key)
|
delete(desiredByNameAndType, key)
|
||||||
@ -136,7 +142,7 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
|||||||
}
|
}
|
||||||
for _, desiredList := range desiredByNameAndType {
|
for _, desiredList := range desiredByNameAndType {
|
||||||
for _, rec := range desiredList {
|
for _, rec := range desiredList {
|
||||||
create = append(create, Correlation{nil, rec})
|
create = append(create, Correlation{d, nil, rec})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -144,10 +150,19 @@ func IncrementalDiff(existing []Record, desired []Record) (unchanged, create, to
|
|||||||
|
|
||||||
func (c Correlation) String() string {
|
func (c Correlation) String() string {
|
||||||
if c.Existing == nil {
|
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 {
|
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
|
package diff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
"github.com/miekg/dns/dnsutil"
|
"github.com/miekg/dns/dnsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myRecord string //@ A 1 1.2.3.4
|
func myRecord(s string) *models.RecordConfig {
|
||||||
|
parts := strings.Split(s, " ")
|
||||||
func (m myRecord) GetName() string {
|
ttl, _ := strconv.ParseUint(parts[2], 10, 32)
|
||||||
name := strings.SplitN(string(m), " ", 4)[0]
|
return &models.RecordConfig{
|
||||||
return dnsutil.AddOrigin(name, "example.com")
|
NameFQDN: dnsutil.AddOrigin(parts[0], "example.com"),
|
||||||
}
|
Type: parts[1],
|
||||||
func (m myRecord) GetType() string {
|
TTL: uint32(ttl),
|
||||||
return strings.SplitN(string(m), " ", 4)[1]
|
Target: parts[3],
|
||||||
}
|
Metadata: map[string]string{},
|
||||||
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 TestAdditionsOnly(t *testing.T) {
|
func TestAdditionsOnly(t *testing.T) {
|
||||||
desired := []Record{
|
desired := []*models.RecordConfig{
|
||||||
myRecord("@ A 1 1.2.3.4"),
|
myRecord("@ A 1 1.2.3.4"),
|
||||||
}
|
}
|
||||||
existing := []Record{}
|
existing := []*models.RecordConfig{}
|
||||||
checkLengths(t, existing, desired, 0, 1, 0, 0)
|
checkLengths(t, existing, desired, 0, 1, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeletionsOnly(t *testing.T) {
|
func TestDeletionsOnly(t *testing.T) {
|
||||||
existing := []Record{
|
existing := []*models.RecordConfig{
|
||||||
myRecord("@ A 1 1.2.3.4"),
|
myRecord("@ A 1 1.2.3.4"),
|
||||||
}
|
}
|
||||||
desired := []Record{}
|
desired := []*models.RecordConfig{}
|
||||||
checkLengths(t, existing, desired, 0, 0, 1, 0)
|
checkLengths(t, existing, desired, 0, 0, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModification(t *testing.T) {
|
func TestModification(t *testing.T) {
|
||||||
existing := []Record{
|
existing := []*models.RecordConfig{
|
||||||
myRecord("www A 1 1.1.1.1"),
|
myRecord("www A 1 1.1.1.1"),
|
||||||
myRecord("@ A 1 1.2.3.4"),
|
myRecord("@ A 1 1.2.3.4"),
|
||||||
}
|
}
|
||||||
desired := []Record{
|
desired := []*models.RecordConfig{
|
||||||
myRecord("@ A 32 1.2.3.4"),
|
myRecord("@ A 32 1.2.3.4"),
|
||||||
myRecord("www A 1 1.1.1.1"),
|
myRecord("www A 1 1.1.1.1"),
|
||||||
}
|
}
|
||||||
un, _, _, mod := checkLengths(t, existing, desired, 1, 0, 0, 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] {
|
if un[0].Desired != desired[1] || un[0].Existing != existing[0] {
|
||||||
t.Error("Expected unchanged records to be correlated")
|
t.Error("Expected unchanged records to be correlated")
|
||||||
}
|
}
|
||||||
@ -62,10 +56,10 @@ func TestModification(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnchangedWithAddition(t *testing.T) {
|
func TestUnchangedWithAddition(t *testing.T) {
|
||||||
existing := []Record{
|
existing := []*models.RecordConfig{
|
||||||
myRecord("www A 1 1.1.1.1"),
|
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.2.3.4"),
|
||||||
myRecord("www A 1 1.1.1.1"),
|
myRecord("www A 1 1.1.1.1"),
|
||||||
}
|
}
|
||||||
@ -76,12 +70,12 @@ func TestUnchangedWithAddition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOutOfOrderRecords(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 1.1.1.1"),
|
||||||
myRecord("www A 1 2.2.2.2"),
|
myRecord("www A 1 2.2.2.2"),
|
||||||
myRecord("www A 1 3.3.3.3"),
|
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 1.1.1.1"),
|
||||||
myRecord("www A 1 2.2.2.2"),
|
myRecord("www A 1 2.2.2.2"),
|
||||||
myRecord("www A 1 2.2.2.3"),
|
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] {
|
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)
|
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) {
|
func TestMxPrio(t *testing.T) {
|
||||||
un, cre, del, mod = IncrementalDiff(existing, desired)
|
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 {
|
if len(un) != unCount {
|
||||||
t.Errorf("Got %d unchanged records, but expected %d", 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 {
|
if len(mod) != modCount {
|
||||||
t.Errorf("Got %d records to modify, but expected %d", len(mod), modCount)
|
t.Errorf("Got %d records to modify, but expected %d", len(mod), modCount)
|
||||||
}
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package gandi
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
"github.com/StackExchange/dnscontrol/providers"
|
"github.com/StackExchange/dnscontrol/providers"
|
||||||
@ -30,49 +28,10 @@ type GandiApi struct {
|
|||||||
ZoneId int64
|
ZoneId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type cfRecord struct {
|
type gandiRecord struct {
|
||||||
gandirecord.RecordInfo
|
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) {
|
func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) {
|
||||||
if err := c.fetchDomainList(); err != nil {
|
if err := c.fetchDomainList(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -95,6 +54,7 @@ func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
|||||||
}
|
}
|
||||||
return ns, nil
|
return ns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
domaininfo, err := c.getDomainInfo(dc.Name)
|
domaininfo, err := c.getDomainInfo(dc.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -104,46 +64,23 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
expectedRecordSets := make([]gandirecord.RecordSet, len(dc.Records))
|
||||||
for i, rec := range dc.Records {
|
for i, rec := range dc.Records {
|
||||||
n := &cfRecord{}
|
if rec.Type == "MX" {
|
||||||
n.Id = 0
|
rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target)
|
||||||
n.Name = rec.Name
|
|
||||||
n.Ttl = int64(rec.TTL)
|
|
||||||
if n.Ttl == 0 {
|
|
||||||
n.Ttl = 3600
|
|
||||||
}
|
}
|
||||||
n.Type = rec.Type
|
if rec.Type == "TXT" {
|
||||||
switch n.Type {
|
rec.Target = "\"" + rec.Target + "\"" // FIXME(tlim): Should do proper quoting.
|
||||||
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
|
|
||||||
}
|
}
|
||||||
expectedDiffRecords[i] = n
|
|
||||||
expectedRecordSets[i] = gandirecord.RecordSet{}
|
expectedRecordSets[i] = gandirecord.RecordSet{}
|
||||||
expectedRecordSets[i]["type"] = n.Type
|
expectedRecordSets[i]["type"] = rec.Type
|
||||||
expectedRecordSets[i]["name"] = n.Name
|
expectedRecordSets[i]["name"] = rec.Name
|
||||||
expectedRecordSets[i]["value"] = n.Value
|
expectedRecordSets[i]["value"] = rec.Target
|
||||||
if n.Ttl != 0 {
|
expectedRecordSets[i]["ttl"] = rec.TTL
|
||||||
expectedRecordSets[i]["ttl"] = n.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
|
// Print a list of changes. Generate an actual change that is the zone
|
||||||
changes := false
|
changes := false
|
||||||
@ -160,7 +97,7 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
|
|||||||
fmt.Println(i)
|
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{}
|
corrections := []*models.Correction{}
|
||||||
if changes {
|
if changes {
|
||||||
corrections = append(corrections,
|
corrections = append(corrections,
|
||||||
|
@ -3,15 +3,13 @@ package gandi
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
|
||||||
)
|
|
||||||
|
|
||||||
import (
|
|
||||||
gandiclient "github.com/prasmussen/gandi-api/client"
|
gandiclient "github.com/prasmussen/gandi-api/client"
|
||||||
gandidomain "github.com/prasmussen/gandi-api/domain"
|
gandidomain "github.com/prasmussen/gandi-api/domain"
|
||||||
gandizone "github.com/prasmussen/gandi-api/domain/zone"
|
gandizone "github.com/prasmussen/gandi-api/domain/zone"
|
||||||
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
|
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
|
||||||
gandiversion "github.com/prasmussen/gandi-api/domain/zone/version"
|
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.
|
// 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.
|
// 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)
|
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
|
||||||
record := gandirecord.New(gc)
|
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.
|
// listZones retrieves the list of zones.
|
||||||
@ -75,11 +81,6 @@ func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
|
|||||||
return zone.Create(name)
|
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) {
|
func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
|
||||||
var zone_id int64
|
var zone_id int64
|
||||||
if zoneinfo.Domains < 2 {
|
if zoneinfo.Domains < 2 {
|
||||||
@ -169,3 +170,13 @@ func (c *GandiApi) createGandiZone(domainname string, zone_id int64, records []g
|
|||||||
|
|
||||||
return nil
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
//convert to dnscontrol RecordConfig format
|
//convert to dnscontrol RecordConfig format
|
||||||
existingRecords := []diff.Record{}
|
existingRecords := []*models.RecordConfig{}
|
||||||
oldRRs := map[key]*dns.ResourceRecordSet{}
|
oldRRs := map[key]*dns.ResourceRecordSet{}
|
||||||
for _, set := range rrs {
|
for _, set := range rrs {
|
||||||
nameWithoutDot := set.Name
|
nameWithoutDot := set.Name
|
||||||
@ -123,11 +123,7 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := []diff.Record{}
|
|
||||||
for _, want := range dc.Records {
|
for _, want := range dc.Records {
|
||||||
if want.TTL == 0 {
|
|
||||||
want.TTL = 300
|
|
||||||
}
|
|
||||||
if want.Type == "MX" {
|
if want.Type == "MX" {
|
||||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
||||||
want.Priority = 0
|
want.Priority = 0
|
||||||
@ -135,24 +131,24 @@ func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correc
|
|||||||
//add quotes to txts
|
//add quotes to txts
|
||||||
want.Target = fmt.Sprintf(`"%s"`, want.Target)
|
want.Target = fmt.Sprintf(`"%s"`, want.Target)
|
||||||
}
|
}
|
||||||
w = append(w, want)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// first collect keys that have changed
|
// 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{}
|
changedKeys := map[key]bool{}
|
||||||
desc := ""
|
desc := ""
|
||||||
for _, c := range create {
|
for _, c := range create {
|
||||||
desc += fmt.Sprintln(c)
|
desc += fmt.Sprintln(c)
|
||||||
changedKeys[keyForRec(c.Desired.(*models.RecordConfig))] = true
|
changedKeys[keyForRec(c.Desired)] = true
|
||||||
}
|
}
|
||||||
for _, d := range delete {
|
for _, d := range delete {
|
||||||
desc += fmt.Sprintln(d)
|
desc += fmt.Sprintln(d)
|
||||||
changedKeys[keyForRec(d.Existing.(*models.RecordConfig))] = true
|
changedKeys[keyForRec(d.Existing)] = true
|
||||||
}
|
}
|
||||||
for _, m := range modify {
|
for _, m := range modify {
|
||||||
desc += fmt.Sprintln(m)
|
desc += fmt.Sprintln(m)
|
||||||
changedKeys[keyForRec(m.Existing.(*models.RecordConfig))] = true
|
changedKeys[keyForRec(m.Existing)] = true
|
||||||
}
|
}
|
||||||
if len(changedKeys) == 0 {
|
if len(changedKeys) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -175,7 +175,11 @@ func TestGetNameservers(t *testing.T) {
|
|||||||
t.Errorf("Test %d: %s", i, err)
|
t.Errorf("Test %d: %s", i, err)
|
||||||
continue
|
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)
|
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/models"
|
||||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultNameservers = []*models.Nameserver{
|
var defaultNameservers = []*models.Nameserver{
|
||||||
@ -23,42 +24,30 @@ func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
actual := make([]diff.Record, len(records))
|
actual := make([]*models.RecordConfig, len(records))
|
||||||
for i := range records {
|
for i, r := range records {
|
||||||
actual[i] = records[i]
|
actual[i] = r.toRecord()
|
||||||
}
|
}
|
||||||
|
|
||||||
desired := make([]diff.Record, 0, len(dc.Records))
|
checkNSModifications(dc)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, create, del, mod := diff.IncrementalDiff(actual, desired)
|
differ := diff.New(dc)
|
||||||
|
_, create, del, mod := differ.IncrementalDiff(actual)
|
||||||
corrections := []*models.Correction{}
|
corrections := []*models.Correction{}
|
||||||
|
|
||||||
for _, d := range del {
|
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) }}
|
c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.RecordID, dc.Name) }}
|
||||||
corrections = append(corrections, c)
|
corrections = append(corrections, c)
|
||||||
}
|
}
|
||||||
for _, cre := range create {
|
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) }}
|
c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
|
||||||
corrections = append(corrections, c)
|
corrections = append(corrections, c)
|
||||||
}
|
}
|
||||||
for _, chng := range mod {
|
for _, chng := range mod {
|
||||||
old := chng.Existing.(*nameComRecord)
|
old := chng.Existing.Original.(*nameComRecord)
|
||||||
new := chng.Desired.(*models.RecordConfig)
|
new := chng.Desired
|
||||||
c := &models.Correction{Msg: chng.String(), F: func() error {
|
c := &models.Correction{Msg: chng.String(), F: func() error {
|
||||||
err := n.deleteRecord(old.RecordID, dc.Name)
|
err := n.deleteRecord(old.RecordID, dc.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -90,21 +79,32 @@ type nameComRecord struct {
|
|||||||
Priority string `json:"priority"`
|
Priority string `json:"priority"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *nameComRecord) GetName() string {
|
func checkNSModifications(dc *models.DomainConfig) {
|
||||||
return r.Name
|
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
||||||
}
|
for _, rec := range dc.Records {
|
||||||
func (r *nameComRecord) GetType() string {
|
if rec.Type == "NS" && rec.NameFQDN == dc.Name {
|
||||||
return r.Type
|
// 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.") {
|
||||||
func (r *nameComRecord) GetContent() string {
|
log.Printf("Warning: name.com does not allow NS records on base domain to be modified. %s will not be added.", rec.Target)
|
||||||
return r.Content
|
}
|
||||||
}
|
continue
|
||||||
func (r *nameComRecord) GetComparisionData() string {
|
}
|
||||||
mxPrio := ""
|
newList = append(newList, rec)
|
||||||
if r.Type == "MX" {
|
}
|
||||||
mxPrio = fmt.Sprintf(" %s ", r.Priority)
|
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 {
|
type listRecordsResponse struct {
|
||||||
|
@ -40,6 +40,7 @@ func init() {
|
|||||||
func sPtr(s string) *string {
|
func sPtr(s string) *string {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route53Provider) getZones() error {
|
func (r *route53Provider) getZones() error {
|
||||||
if r.zones != nil {
|
if r.zones != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -73,8 +74,8 @@ type key struct {
|
|||||||
Name, Type string
|
Name, Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKey(r diff.Record) key {
|
func getKey(r *models.RecordConfig) key {
|
||||||
return key{r.GetName(), r.GetType()}
|
return key{r.NameFQDN, r.Type}
|
||||||
}
|
}
|
||||||
|
|
||||||
type errNoExist struct {
|
type errNoExist struct {
|
||||||
@ -121,8 +122,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//convert to dnscontrol RecordConfig format
|
var existingRecords = []*models.RecordConfig{}
|
||||||
var existingRecords = []diff.Record{}
|
|
||||||
for _, set := range records {
|
for _, set := range records {
|
||||||
for _, rec := range set.ResourceRecords {
|
for _, rec := range set.ResourceRecords {
|
||||||
if *set.Type == "SOA" {
|
if *set.Type == "SOA" {
|
||||||
@ -137,23 +137,19 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||||||
existingRecords = append(existingRecords, r)
|
existingRecords = append(existingRecords, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w := []diff.Record{}
|
|
||||||
for _, want := range dc.Records {
|
for _, want := range dc.Records {
|
||||||
if want.TTL == 0 {
|
|
||||||
want.TTL = 300
|
|
||||||
}
|
|
||||||
if want.Type == "MX" {
|
if want.Type == "MX" {
|
||||||
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
|
||||||
want.Priority = 0
|
want.Priority = 0
|
||||||
} else if want.Type == "TXT" {
|
} else if want.Type == "TXT" {
|
||||||
want.Target = fmt.Sprintf(`"%s"`, want.Target) //FIXME: better escaping/quoting
|
want.Target = fmt.Sprintf(`"%s"`, want.Target) //FIXME: better escaping/quoting
|
||||||
}
|
}
|
||||||
w = append(w, want)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//diff
|
//diff
|
||||||
changeDesc := ""
|
changeDesc := ""
|
||||||
_, create, delete, modify := diff.IncrementalDiff(existingRecords, w)
|
differ := diff.New(dc)
|
||||||
|
_, create, delete, modify := differ.IncrementalDiff(existingRecords)
|
||||||
|
|
||||||
namesToUpdate := map[key]bool{}
|
namesToUpdate := map[key]bool{}
|
||||||
for _, c := range create {
|
for _, c := range create {
|
||||||
|
Reference in New Issue
Block a user