github.com/kurthockenbury/dnscontrol@v0.2.8/providers/gandi/gandiProvider.go (about) 1 package gandi 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 8 "github.com/StackExchange/dnscontrol/models" 9 "github.com/StackExchange/dnscontrol/pkg/printer" 10 "github.com/StackExchange/dnscontrol/providers" 11 "github.com/StackExchange/dnscontrol/providers/diff" 12 "github.com/pkg/errors" 13 14 "strings" 15 16 gandidomain "github.com/prasmussen/gandi-api/domain" 17 gandirecord "github.com/prasmussen/gandi-api/domain/zone/record" 18 ) 19 20 /* 21 22 Gandi API DNS provider: 23 24 Info required in `creds.json`: 25 - apikey 26 27 */ 28 29 var features = providers.DocumentationNotes{ 30 providers.CanUseCAA: providers.Can(), 31 providers.CanUsePTR: providers.Can(), 32 providers.CanUseSRV: providers.Can(), 33 providers.CantUseNOPURGE: providers.Cannot(), 34 providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"), 35 providers.DocOfficiallySupported: providers.Cannot(), 36 } 37 38 func init() { 39 providers.RegisterDomainServiceProviderType("GANDI", newDsp, features) 40 providers.RegisterRegistrarType("GANDI", newReg) 41 } 42 43 // GandiApi is the API handle for this module. 44 type GandiApi struct { 45 ApiKey string 46 domainIndex map[string]int64 // Map of domainname to index 47 nameservers map[string][]*models.Nameserver 48 ZoneId int64 49 } 50 51 type gandiRecord struct { 52 gandirecord.RecordInfo 53 } 54 55 func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) { 56 if err := c.fetchDomainList(); err != nil { 57 return nil, err 58 } 59 _, ok := c.domainIndex[domain] 60 if !ok { 61 return nil, errors.Errorf("%s not listed in zones for gandi account", domain) 62 } 63 return c.fetchDomainInfo(domain) 64 } 65 66 // GetNameservers returns the nameservers for domain. 67 func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) { 68 domaininfo, err := c.getDomainInfo(domain) 69 if err != nil { 70 return nil, err 71 } 72 ns := []*models.Nameserver{} 73 for _, nsname := range domaininfo.Nameservers { 74 ns = append(ns, &models.Nameserver{Name: nsname}) 75 } 76 return ns, nil 77 } 78 79 // GetDomainCorrections returns a list of corrections recommended for this domain. 80 func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 81 dc.Punycode() 82 domaininfo, err := c.getDomainInfo(dc.Name) 83 if err != nil { 84 return nil, err 85 } 86 foundRecords, err := c.getZoneRecords(domaininfo.ZoneId, dc.Name) 87 if err != nil { 88 return nil, err 89 } 90 91 expectedRecordSets := make([]gandirecord.RecordSet, 0, len(dc.Records)) 92 recordsToKeep := make([]*models.RecordConfig, 0, len(dc.Records)) 93 for _, rec := range dc.Records { 94 if rec.TTL < 300 { 95 printer.Warnf("Gandi does not support ttls < 300. Setting %s from %d to 300\n", rec.GetLabelFQDN(), rec.TTL) 96 rec.TTL = 300 97 } 98 if rec.TTL > 2592000 { 99 return nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL) 100 } 101 if rec.Type == "TXT" { 102 rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting. 103 } 104 if rec.Type == "NS" && rec.GetLabel() == "@" { 105 if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") { 106 printer.Warnf("Gandi does not support changing apex NS records. %s will not be added.\n", rec.GetTargetField()) 107 } 108 continue 109 } 110 rs := gandirecord.RecordSet{ 111 "type": rec.Type, 112 "name": rec.GetLabel(), 113 "value": rec.GetTargetCombined(), 114 "ttl": rec.TTL, 115 } 116 expectedRecordSets = append(expectedRecordSets, rs) 117 recordsToKeep = append(recordsToKeep, rec) 118 } 119 dc.Records = recordsToKeep 120 121 // Normalize 122 models.PostProcessRecords(foundRecords) 123 124 differ := diff.New(dc) 125 _, create, del, mod := differ.IncrementalDiff(foundRecords) 126 127 // Print a list of changes. Generate an actual change that is the zone 128 changes := false 129 desc := "" 130 for _, i := range create { 131 changes = true 132 desc += "\n" + i.String() 133 } 134 for _, i := range del { 135 changes = true 136 desc += "\n" + i.String() 137 } 138 for _, i := range mod { 139 changes = true 140 desc += "\n" + i.String() 141 } 142 143 msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc) 144 corrections := []*models.Correction{} 145 if changes { 146 corrections = append(corrections, 147 &models.Correction{ 148 Msg: msg, 149 F: func() error { 150 printer.Printf("CREATING ZONE: %v\n", dc.Name) 151 return c.createGandiZone(dc.Name, domaininfo.ZoneId, expectedRecordSets) 152 }, 153 }) 154 } 155 156 return corrections, nil 157 } 158 159 func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { 160 return newGandi(conf, metadata) 161 } 162 163 func newReg(conf map[string]string) (providers.Registrar, error) { 164 return newGandi(conf, nil) 165 } 166 167 func newGandi(m map[string]string, metadata json.RawMessage) (*GandiApi, error) { 168 api := &GandiApi{} 169 api.ApiKey = m["apikey"] 170 if api.ApiKey == "" { 171 return nil, errors.Errorf("missing Gandi apikey") 172 } 173 174 return api, nil 175 } 176 177 // GetRegistrarCorrections returns a list of corrections for this registrar. 178 func (c *GandiApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 179 domaininfo, err := c.getDomainInfo(dc.Name) 180 if err != nil { 181 return nil, err 182 } 183 sort.Strings(domaininfo.Nameservers) 184 found := strings.Join(domaininfo.Nameservers, ",") 185 desiredNs := []string{} 186 for _, d := range dc.Nameservers { 187 desiredNs = append(desiredNs, d.Name) 188 } 189 sort.Strings(desiredNs) 190 desired := strings.Join(desiredNs, ",") 191 if found != desired { 192 return []*models.Correction{ 193 { 194 Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired), 195 F: func() (err error) { 196 _, err = c.setDomainNameservers(dc.Name, desiredNs) 197 return 198 }}, 199 }, nil 200 } 201 return nil, nil 202 }