1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00
Files
stackexchange-dnscontrol/providers/ovh/protocol.go
Brice Figureau e44dde52e2 New Provider: OVH DNS Provider (#143) (#175)
* OVH DNS Provider (#143)

This adds the OVH Provider along with its documentation.

Unfortunately we can't set this DNS provider to support `CanUsePTR`,
because OVH only supports setting PTR target on the Arpa zone.

* OVH Registrar provider (#143)

This implements OVH as a registrar provider.
Note that NS modifications are done in a "best effort" mode, as the
provider doesn't wait for the modifications to be fully applied
(the operation that can take a long time).

* Allow support for dual providers scenarios

Since OVH released their APIv6, it is now possible to update
zone apex NS records, opening the door to complete dual providers
scenarii.

This change implements apex NS management in an OVH zone.
2017-11-10 11:02:34 -08:00

258 lines
6.7 KiB
Go

package ovh
import (
"fmt"
"github.com/StackExchange/dnscontrol/models"
"github.com/miekg/dns/dnsutil"
)
type Void struct {
}
// fetchDomainList gets list of zones for account
func (c *ovhProvider) fetchZones() error {
if c.zones != nil {
return nil
}
c.zones = map[string]bool{}
var response []string
err := c.client.Call("GET", "/domain/zone", nil, &response)
if err != nil {
return err
}
for _, d := range response {
c.zones[d] = true
}
return nil
}
type Zone struct {
LastUpdate string `json:"lastUpdate,omitempty"`
HasDNSAnycast bool `json:"hasDNSAnycast,omitempty"`
NameServers []string `json:"nameServers"`
DNSSecSupported bool `json:"dnssecSupported"`
}
// get info about a zone.
func (c *ovhProvider) fetchZone(fqdn string) (*Zone, error) {
var response Zone
err := c.client.Call("GET", "/domain/zone/"+fqdn, nil, &response)
if err != nil {
return nil, err
}
return &response, nil
}
type Record struct {
Target string `json:"target,omitempty"`
Zone string `json:"zone,omitempty"`
TTL uint32 `json:"ttl,omitempty"`
FieldType string `json:"fieldType,omitempty"`
Id int64 `json:"id,omitempty"`
SubDomain string `json:"subDomain,omitempty"`
}
type records struct {
recordsId []int
}
func (c *ovhProvider) fetchRecords(fqdn string) ([]*Record, error) {
var recordIds []int
err := c.client.Call("GET", "/domain/zone/"+fqdn+"/record", nil, &recordIds)
if err != nil {
return nil, err
}
records := make([]*Record, len(recordIds))
for i, id := range recordIds {
r, err := c.fecthRecord(fqdn, id)
if err != nil {
return nil, err
}
records[i] = r
}
return records, nil
}
func (c *ovhProvider) fecthRecord(fqdn string, id int) (*Record, error) {
var response Record
err := c.client.Call("GET", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, &response)
if err != nil {
return nil, err
}
return &response, nil
}
// Returns a function that can be invoked to delete a record in a zone.
func (c *ovhProvider) deleteRecordFunc(id int64, fqdn string) func() error {
return func() error {
err := c.client.Call("DELETE", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, nil)
if err != nil {
return err
}
return nil
}
}
// Returns a function that can be invoked to create a record in a zone.
func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) func() error {
return func() error {
record := Record{
SubDomain: dnsutil.TrimDomainName(rc.NameFQDN, fqdn),
FieldType: rc.Type,
Target: rc.Content(),
TTL: rc.TTL,
}
if record.SubDomain == "@" {
record.SubDomain = ""
}
var response Record
err := c.client.Call("POST", fmt.Sprintf("/domain/zone/%s/record", fqdn), &record, &response)
return err
}
}
// Returns a function that can be invoked to update a record in a zone.
func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqdn string) func() error {
return func() error {
record := Record{
SubDomain: dnsutil.TrimDomainName(rc.NameFQDN, fqdn),
FieldType: rc.Type,
Target: rc.Content(),
TTL: rc.TTL,
Zone: fqdn,
Id: old.Id,
}
if record.SubDomain == "@" {
record.SubDomain = ""
}
return c.client.Call("PUT", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, old.Id), &record, &Void{})
}
}
func (c *ovhProvider) refreshZone(fqdn string) error {
return c.client.Call("POST", fmt.Sprintf("/domain/zone/%s/refresh", fqdn), nil, &Void{})
}
// fetch the NS OVH attributed to this zone (which is distinct from fetchRealNS which
// get the exact NS stored at the registrar
func (c *ovhProvider) fetchNS(fqdn string) ([]string, error) {
zone, err := c.fetchZone(fqdn)
if err != nil {
return nil, err
}
return zone.NameServers, nil
}
type CurrentNameServer struct {
ToDelete bool `json:"toDelete,omitempty"`
Ip string `json:"ip,omitempty"`
IsUsed bool `json:"isUsed,omitempty"`
Id int `json:"id,omitempty"`
Host string `json:"host,omitempty"`
}
// Retrieve the NS currently being deployed to the registrar
func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) {
var nameServersId []int
err := c.client.Call("GET", "/domain/"+fqdn+"/nameServer", nil, &nameServersId)
if err != nil {
return nil, err
}
var nameServers []string
for _, id := range nameServersId {
var ns CurrentNameServer
err = c.client.Call("GET", fmt.Sprintf("/domain/%s/nameServer/%d", fqdn, id), nil, &ns)
if err != nil {
return nil, err
}
// skip NS that we asked for deletion
if ns.ToDelete {
continue
}
nameServers = append(nameServers, ns.Host)
}
return nameServers, nil
}
type DomainNS struct {
Host string `json:"host,omitempty"`
Ip string `json:"ip,omitempty"`
}
type UpdateNS struct {
NameServers []DomainNS `json:"nameServers"`
}
type Task struct {
Function string `json:"function,omitempty"`
Status string `json:"status,omitempty"`
CanAccelerate bool `json:"canAccelerate,omitempty"`
LastUpdate string `json:"lastUpdate,omitempty"`
CreationDate string `json:"creationDate,omitempty"`
Comment string `json:"comment,omitempty"`
TodoDate string `json:"todoDate,omitempty"`
Id int64 `json:"id,omitempty"`
CanCancel bool `json:"canCancel,omitempty"`
DoneDate string `json:"doneDate,omitempty"`
CanRelaunch bool `json:"canRelaunch,omitempty"`
}
type Domain struct {
NameServerType string `json:"nameServerType,omitempty"`
TransferLockStatus string `json:"transferLockStatus,omitempty"`
}
func (c *ovhProvider) updateNS(fqdn string, ns []string) error {
// we first need to make sure we can edit the NS
// by default zones are in "hosted" mode meaning they default
// to OVH default NS. In this mode, the NS can't be updated.
domain := Domain{NameServerType: "external"}
err := c.client.Call("PUT", fmt.Sprintf("/domain/%s", fqdn), &domain, &Void{})
if err != nil {
return err
}
var newNs []DomainNS
for _, n := range ns {
newNs = append(newNs, DomainNS{
Host: n,
})
}
update := UpdateNS{
NameServers: newNs,
}
var task Task
err = c.client.Call("POST", fmt.Sprintf("/domain/%s/nameServers/update", fqdn), &update, &task)
if err != nil {
return err
}
if task.Status == "error" {
return fmt.Errorf("API error while updating ns for %s: %s", fqdn, task.Comment)
}
// we don't wait for the task execution. One of the reason is that
// NS modification can take time in the registrar, the other is that every task
// in OVH is usually executed a few minutes after they have been registered.
// We count on the fact that `GetNameservers` uses the registrar API to get
// a coherent view (including pending modifications) of the registered NS.
return nil
}