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