From d205c8b4ed183c13f6de41caa26156b6f0509cda Mon Sep 17 00:00:00 2001 From: Craig Peterson Date: Mon, 27 Mar 2017 16:03:01 -0600 Subject: [PATCH] Adding gandi to integration suite. Fixing bugs. (#57) * Adding gandi to integration suite. Fixing bugs. Fixes #35 Fixes #36 * small fixes * gandi docs --- docs/_providers/dnsimple.md | 3 ++- docs/_providers/gandi.md | 38 ++++++++++++++++++++++++++ integrationTest/providers.json | 7 +++++ models/dns.go | 11 ++++++++ providers/gandi/gandiProvider.go | 46 ++++++++++++++++++++++---------- providers/gandi/protocol.go | 26 +++++++++--------- 6 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 docs/_providers/gandi.md diff --git a/docs/_providers/dnsimple.md b/docs/_providers/dnsimple.md index 2acb9abad..d9cc3e079 100644 --- a/docs/_providers/dnsimple.md +++ b/docs/_providers/dnsimple.md @@ -26,7 +26,8 @@ This provider does not recognize any special metadata fields unique to DNSimple. Example javascript: {% highlight js %} -var DNSIMPLE = NewDnsProvider("dnsimple", DNSIMPLE); +var REG_DNSIMPLE = NewRegistrar("dnsimple", "DNSIMPLE"); +var DNSIMPLE = NewDnsProvider("dnsimple", "DNSIMPLE"); D("example.tld", REG_DNSIMPLE, DnsProvider(DNSIMPLE), A("test","1.2.3.4") diff --git a/docs/_providers/gandi.md b/docs/_providers/gandi.md new file mode 100644 index 000000000..a25388263 --- /dev/null +++ b/docs/_providers/gandi.md @@ -0,0 +1,38 @@ +--- +name: Gandi +layout: default +jsId: GANDI +--- +# Gandi Provider + +Gandi provides bot a registrar and a dns provider implementation. + +## Configuration + +In your providers config json file you must provide your Gandi.net api key: + +{% highlight json %} +{ + "gandi":{ + "apikey": "your-gandi-key" + } +} +{% endhighlight %} + +## Metadata + +This provider does not recognize any special metadata fields unique to DNSimple. + +## Usage + +Example javascript: + +{% highlight js %} +var REG_GANDI = NewRegistrar("gandi", "GANDI"); +var GANDI = NewDnsProvider("gandi", "GANDI"); + +D("example.tld", REG_GANDI, DnsProvider(GANDI), + A("test","1.2.3.4") +); +{% endhighlight %} + diff --git a/integrationTest/providers.json b/integrationTest/providers.json index b0c90e463..43bcf5194 100644 --- a/integrationTest/providers.json +++ b/integrationTest/providers.json @@ -3,11 +3,18 @@ "domain": "example.com" }, "DNSIMPLE": { + //16/17: no ns records managable. Not even for subdomains. "knownFailures": "16,17", "domain": "$DNSIMPLE_DOMAIN", "token": "$DNSIMPLE_TOKEN", "baseurl": "https://api.sandbox.dnsimple.com" }, + "GANDI":{ + //5: gandi does not accept ttls less than 300 + "knownFailures": "5", + "domain": "$GANDI_DOMAIN", + "apikey": "$GANDI_KEY" + }, "GCLOUD": { "domain": "$GCLOUD_DOMAIN", "project_id": "$GCLOUD_PROJECT", diff --git a/models/dns.go b/models/dns.go index 928757973..79d1604c3 100644 --- a/models/dns.go +++ b/models/dns.go @@ -191,6 +191,17 @@ func (dc *DomainConfig) Punycode() error { return nil } +// CombineMXs will merge the priority into the target field for all mx records. +// Useful for providers that desire them as one field. +func (dc *DomainConfig) CombineMXs() { + for _, rec := range dc.Records { + if rec.Type == "MX" { + rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target) + rec.Priority = 0 + } + } +} + func copyObj(input interface{}, output interface{}) error { buf := &bytes.Buffer{} enc := gob.NewEncoder(buf) diff --git a/providers/gandi/gandiProvider.go b/providers/gandi/gandiProvider.go index 80043d707..27dcaffca 100644 --- a/providers/gandi/gandiProvider.go +++ b/providers/gandi/gandiProvider.go @@ -3,11 +3,14 @@ package gandi import ( "encoding/json" "fmt" + "log" "github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers/diff" + "strings" + gandidomain "github.com/prasmussen/gandi-api/domain" gandirecord "github.com/prasmussen/gandi-api/domain/zone/record" ) @@ -56,48 +59,63 @@ func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) { } func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { + dc.Punycode() + dc.CombineMXs() domaininfo, err := c.getDomainInfo(dc.Name) if err != nil { return nil, err } - foundRecords, err := c.getZoneRecords(domaininfo.ZoneId) + foundRecords, err := c.getZoneRecords(domaininfo.ZoneId, dc.Name) if err != nil { return nil, err } - expectedRecordSets := make([]gandirecord.RecordSet, len(dc.Records)) - for i, rec := range dc.Records { - if rec.Type == "MX" { - rec.Target = fmt.Sprintf("%d %s", rec.Priority, rec.Target) + expectedRecordSets := make([]gandirecord.RecordSet, 0, len(dc.Records)) + recordsToKeep := make([]*models.RecordConfig, 0, len(dc.Records)) + for _, rec := range dc.Records { + if rec.TTL < 300 { + log.Printf("WARNING: Gandi does not support ttls < 300. %s will not be set to %d.", rec.NameFQDN, rec.TTL) + rec.TTL = 300 } if rec.Type == "TXT" { rec.Target = "\"" + rec.Target + "\"" // FIXME(tlim): Should do proper quoting. } - expectedRecordSets[i] = gandirecord.RecordSet{} - expectedRecordSets[i]["type"] = rec.Type - expectedRecordSets[i]["name"] = rec.Name - expectedRecordSets[i]["value"] = rec.Target - expectedRecordSets[i]["ttl"] = rec.TTL + if rec.Type == "NS" && rec.Name == "@" { + if !strings.HasSuffix(rec.Target, ".gandi.net.") { + log.Printf("WARNING: Gandi does not support changing apex NS records. %s will not be added.", rec.Target) + } + continue + } + rs := gandirecord.RecordSet{ + "type": rec.Type, + "name": rec.Name, + "value": rec.Target, + "ttl": rec.TTL, + } + expectedRecordSets = append(expectedRecordSets, rs) + recordsToKeep = append(recordsToKeep, rec) } + dc.Records = recordsToKeep 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 + desc := "" for _, i := range create { changes = true - fmt.Println(i) + desc += "\n" + i.String() } for _, i := range del { changes = true - fmt.Println(i) + desc += "\n" + i.String() } for _, i := range mod { changes = true - fmt.Println(i) + desc += "\n" + i.String() } - msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)", dc.Name, len(dc.Records)) + msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc) corrections := []*models.Correction{} if changes { corrections = append(corrections, diff --git a/providers/gandi/protocol.go b/providers/gandi/protocol.go index 6f623359a..9bac47cbd 100644 --- a/providers/gandi/protocol.go +++ b/providers/gandi/protocol.go @@ -10,6 +10,7 @@ import ( gandiversion "github.com/prasmussen/gandi-api/domain/zone/version" "github.com/StackExchange/dnscontrol/models" + "github.com/miekg/dns/dnsutil" ) // fetchDomainList gets list of domains for account. Cache ids for easy lookup. @@ -39,7 +40,7 @@ func (c *GandiApi) fetchDomainInfo(fqdn string) (*gandidomain.DomainInfo, error) } // getRecordsForDomain returns a list of records for a zone. -func (c *GandiApi) getZoneRecords(zoneid int64) ([]*models.RecordConfig, error) { +func (c *GandiApi) getZoneRecords(zoneid int64, origin string) ([]*models.RecordConfig, error) { gc := gandiclient.New(c.ApiKey, gandiclient.Production) record := gandirecord.New(gc) recs, err := record.List(zoneid, 0) @@ -48,7 +49,7 @@ func (c *GandiApi) getZoneRecords(zoneid int64) ([]*models.RecordConfig, error) } rcs := make([]*models.RecordConfig, 0, len(recs)) for _, r := range recs { - rcs = append(rcs, convert(r)) + rcs = append(rcs, convert(r, origin)) } return rcs, nil } @@ -133,37 +134,37 @@ func (c *GandiApi) activateVersion(zone_id, version_id int64) (bool, error) { return version.Set(zone_id, version_id) } -func (c *GandiApi) createGandiZone(domainname string, zone_id int64, records []gandirecord.RecordSet) error { +func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error { // Get the zone_id of the zone we'll be updating. - zoneinfo, err := c.getZoneInfo(zone_id) + zoneinfo, err := c.getZoneInfo(zoneID) if err != nil { return err } //fmt.Println("ZONEINFO:", zoneinfo) - zone_id, err = c.getEditableZone(domainname, zoneinfo) + zoneID, err = c.getEditableZone(domainname, zoneinfo) if err != nil { return err } - // Get the version_id of the zone we're updating. - version_id, err := c.makeEditableZone(zone_id) + // Get the versionID of the zone we're updating. + versionID, err := c.makeEditableZone(zoneID) if err != nil { return err } // Update the new version. - _, err = c.setZoneRecords(zone_id, version_id, records) + _, err = c.setZoneRecords(zoneID, versionID, records) if err != nil { return err } // Activate zone version - _, err = c.activateVersion(zone_id, version_id) + _, err = c.activateVersion(zoneID, versionID) if err != nil { return err } - _, err = c.setZones(domainname, zone_id) + _, err = c.setZones(domainname, zoneID) if err != nil { return err } @@ -171,9 +172,10 @@ func (c *GandiApi) createGandiZone(domainname string, zone_id int64, records []g return nil } -func convert(r *gandirecord.RecordInfo) *models.RecordConfig { +func convert(r *gandirecord.RecordInfo, origin string) *models.RecordConfig { return &models.RecordConfig{ - NameFQDN: r.Name, + NameFQDN: dnsutil.AddOrigin(r.Name, origin), + Name: r.Name, Type: r.Type, Original: r, Target: r.Value,