github.com/StackExchange/DNSControl@v0.2.8/providers/ovh/ovhProvider.go (about) 1 package ovh 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/StackExchange/dnscontrol/models" 10 "github.com/StackExchange/dnscontrol/providers" 11 "github.com/StackExchange/dnscontrol/providers/diff" 12 "github.com/pkg/errors" 13 "github.com/xlucas/go-ovh/ovh" 14 ) 15 16 type ovhProvider struct { 17 client *ovh.Client 18 zones map[string]bool 19 } 20 21 var features = providers.DocumentationNotes{ 22 providers.CanUseAlias: providers.Cannot(), 23 providers.CanUseCAA: providers.Cannot(), 24 providers.CanUsePTR: providers.Cannot(), 25 providers.CanUseSRV: providers.Can(), 26 providers.CanUseTLSA: providers.Can(), 27 providers.DocCreateDomains: providers.Cannot("New domains require registration"), 28 providers.DocDualHost: providers.Can(), 29 providers.DocOfficiallySupported: providers.Cannot(), 30 } 31 32 func newOVH(m map[string]string, metadata json.RawMessage) (*ovhProvider, error) { 33 appKey, appSecretKey, consumerKey := m["app-key"], m["app-secret-key"], m["consumer-key"] 34 35 c := ovh.NewClient(ovh.ENDPOINT_EU_OVHCOM, appKey, appSecretKey, consumerKey, false) 36 37 // Check for time lag 38 if err := c.PollTimeshift(); err != nil { 39 return nil, err 40 } 41 42 ovh := &ovhProvider{client: c} 43 if err := ovh.fetchZones(); err != nil { 44 return nil, err 45 } 46 return ovh, nil 47 } 48 49 func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { 50 return newOVH(conf, metadata) 51 } 52 53 func newReg(conf map[string]string) (providers.Registrar, error) { 54 return newOVH(conf, nil) 55 } 56 57 func init() { 58 providers.RegisterRegistrarType("OVH", newReg) 59 providers.RegisterDomainServiceProviderType("OVH", newDsp, features) 60 } 61 62 func (c *ovhProvider) GetNameservers(domain string) ([]*models.Nameserver, error) { 63 _, ok := c.zones[domain] 64 if !ok { 65 return nil, errors.Errorf("%s not listed in zones for ovh account", domain) 66 } 67 68 ns, err := c.fetchNS(domain) 69 if err != nil { 70 return nil, err 71 } 72 73 return models.StringsToNameservers(ns), nil 74 } 75 76 type errNoExist struct { 77 domain string 78 } 79 80 func (e errNoExist) Error() string { 81 return fmt.Sprintf("Domain %s not found in your ovh account", e.domain) 82 } 83 84 func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 85 dc.Punycode() 86 //dc.CombineMXs() 87 88 if !c.zones[dc.Name] { 89 return nil, errNoExist{dc.Name} 90 } 91 92 records, err := c.fetchRecords(dc.Name) 93 if err != nil { 94 return nil, err 95 } 96 97 var actual []*models.RecordConfig 98 for _, r := range records { 99 rec := nativeToRecord(r, dc.Name) 100 if rec != nil { 101 actual = append(actual, rec) 102 } 103 } 104 105 // Normalize 106 models.PostProcessRecords(actual) 107 108 differ := diff.New(dc) 109 _, create, delete, modify := differ.IncrementalDiff(actual) 110 111 corrections := []*models.Correction{} 112 113 for _, del := range delete { 114 rec := del.Existing.Original.(*Record) 115 corrections = append(corrections, &models.Correction{ 116 Msg: del.String(), 117 F: c.deleteRecordFunc(rec.ID, dc.Name), 118 }) 119 } 120 121 for _, cre := range create { 122 rec := cre.Desired 123 corrections = append(corrections, &models.Correction{ 124 Msg: cre.String(), 125 F: c.createRecordFunc(rec, dc.Name), 126 }) 127 } 128 129 for _, mod := range modify { 130 oldR := mod.Existing.Original.(*Record) 131 newR := mod.Desired 132 corrections = append(corrections, &models.Correction{ 133 Msg: mod.String(), 134 F: c.updateRecordFunc(oldR, newR, dc.Name), 135 }) 136 } 137 138 if len(corrections) > 0 { 139 corrections = append(corrections, &models.Correction{ 140 Msg: "REFRESH zone " + dc.Name, 141 F: func() error { 142 return c.refreshZone(dc.Name) 143 }, 144 }) 145 } 146 147 return corrections, nil 148 } 149 150 func nativeToRecord(r *Record, origin string) *models.RecordConfig { 151 if r.FieldType == "SOA" { 152 return nil 153 } 154 rec := &models.RecordConfig{ 155 TTL: uint32(r.TTL), 156 Original: r, 157 } 158 159 rtype := r.FieldType 160 161 // ovh uses a custom type for SPF and DKIM 162 if rtype == "SPF" || rtype == "DKIM" { 163 rtype = "TXT" 164 } 165 166 rec.SetLabel(r.SubDomain, origin) 167 if err := rec.PopulateFromString(rtype, r.Target, origin); err != nil { 168 panic(errors.Wrap(err, "unparsable record received from ovh")) 169 } 170 171 // ovh default is 3600 172 if rec.TTL == 0 { 173 rec.TTL = 3600 174 } 175 176 return rec 177 } 178 179 func (c *ovhProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 180 181 ns, err := c.fetchRegistrarNS(dc.Name) 182 if err != nil { 183 return nil, err 184 } 185 186 sort.Strings(ns) 187 found := strings.Join(ns, ",") 188 189 desiredNs := []string{} 190 for _, d := range dc.Nameservers { 191 desiredNs = append(desiredNs, d.Name) 192 } 193 sort.Strings(desiredNs) 194 desired := strings.Join(desiredNs, ",") 195 196 if found != desired { 197 return []*models.Correction{ 198 { 199 Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired), 200 F: func() error { 201 err := c.updateNS(dc.Name, desiredNs) 202 if err != nil { 203 return err 204 } 205 return nil 206 }}, 207 }, nil 208 } 209 return nil, nil 210 }