2017-11-11 03:02:34 +08:00
package ovh
import (
"fmt"
2019-05-21 10:31:59 +08:00
"strings"
2018-01-10 01:53:16 +08:00
2020-01-28 23:42:31 +08:00
"github.com/StackExchange/dnscontrol/v2/models"
2017-11-11 03:02:34 +08:00
"github.com/miekg/dns/dnsutil"
)
2018-01-10 01:53:16 +08:00
// Void an empty structure.
2017-11-11 03:02:34 +08:00
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
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "GET" , "/domain/zone" , nil , & response , true )
2017-11-11 03:02:34 +08:00
if err != nil {
return err
}
for _ , d := range response {
c . zones [ d ] = true
}
return nil
}
2018-01-10 01:53:16 +08:00
// Zone describes the attributes of a DNS zone.
2017-11-11 03:02:34 +08:00
type Zone struct {
2019-05-23 21:25:26 +08:00
DNSSecSupported bool ` json:"dnssecSupported" `
2017-11-11 03:02:34 +08:00
HasDNSAnycast bool ` json:"hasDNSAnycast,omitempty" `
NameServers [ ] string ` json:"nameServers" `
2019-05-23 21:25:26 +08:00
LastUpdate string ` json:"lastUpdate,omitempty" `
2017-11-11 03:02:34 +08:00
}
// get info about a zone.
func ( c * ovhProvider ) fetchZone ( fqdn string ) ( * Zone , error ) {
var response Zone
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "GET" , "/domain/zone/" + fqdn , nil , & response , true )
2017-11-11 03:02:34 +08:00
if err != nil {
return nil , err
}
return & response , nil
}
2018-01-10 01:53:16 +08:00
// Record describes a DNS record.
2017-11-11 03:02:34 +08:00
type Record struct {
Target string ` json:"target,omitempty" `
Zone string ` json:"zone,omitempty" `
TTL uint32 ` json:"ttl,omitempty" `
FieldType string ` json:"fieldType,omitempty" `
2018-01-10 01:53:16 +08:00
ID int64 ` json:"id,omitempty" `
2017-11-11 03:02:34 +08:00
SubDomain string ` json:"subDomain,omitempty" `
}
type records struct {
2018-01-10 01:53:16 +08:00
recordsID [ ] int
2017-11-11 03:02:34 +08:00
}
func ( c * ovhProvider ) fetchRecords ( fqdn string ) ( [ ] * Record , error ) {
var recordIds [ ] int
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "GET" , "/domain/zone/" + fqdn + "/record" , nil , & recordIds , true )
2017-11-11 03:02:34 +08:00
if err != nil {
return nil , err
}
records := make ( [ ] * Record , len ( recordIds ) )
for i , id := range recordIds {
2019-05-21 10:31:59 +08:00
r , err := c . fetchRecord ( fqdn , id )
2017-11-11 03:02:34 +08:00
if err != nil {
return nil , err
}
records [ i ] = r
}
return records , nil
}
2019-05-21 10:31:59 +08:00
func ( c * ovhProvider ) fetchRecord ( fqdn string , id int ) ( * Record , error ) {
2017-11-11 03:02:34 +08:00
var response Record
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "GET" , fmt . Sprintf ( "/domain/zone/%s/record/%d" , fqdn , id ) , nil , & response , true )
2017-11-11 03:02:34 +08:00
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 {
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "DELETE" , fmt . Sprintf ( "/domain/zone/%s/record/%d" , fqdn , id ) , nil , nil , true )
2017-11-11 03:02:34 +08:00
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 {
2019-05-21 10:31:59 +08:00
if c . isDKIMRecord ( rc ) {
rc . Type = "DKIM"
}
2017-11-11 03:02:34 +08:00
record := Record {
2018-03-20 05:18:58 +08:00
SubDomain : dnsutil . TrimDomainName ( rc . GetLabelFQDN ( ) , fqdn ) ,
2017-11-11 03:02:34 +08:00
FieldType : rc . Type ,
2018-02-16 01:02:50 +08:00
Target : rc . GetTargetCombined ( ) ,
2017-11-11 03:02:34 +08:00
TTL : rc . TTL ,
}
if record . SubDomain == "@" {
record . SubDomain = ""
}
var response Record
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "POST" , fmt . Sprintf ( "/domain/zone/%s/record" , fqdn ) , & record , & response , true )
2017-11-11 03:02:34 +08:00
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 {
2019-05-21 10:31:59 +08:00
if c . isDKIMRecord ( rc ) {
rc . Type = "DKIM"
}
2017-11-11 03:02:34 +08:00
record := Record {
2018-03-20 05:18:58 +08:00
SubDomain : rc . GetLabel ( ) ,
2017-11-11 03:02:34 +08:00
FieldType : rc . Type ,
2018-02-16 01:02:50 +08:00
Target : rc . GetTargetCombined ( ) ,
2017-11-11 03:02:34 +08:00
TTL : rc . TTL ,
Zone : fqdn ,
2018-01-10 01:53:16 +08:00
ID : old . ID ,
2017-11-11 03:02:34 +08:00
}
if record . SubDomain == "@" {
record . SubDomain = ""
}
2019-05-21 10:31:59 +08:00
err := c . client . CallAPI ( "PUT" , fmt . Sprintf ( "/domain/zone/%s/record/%d" , fqdn , old . ID ) , & record , & Void { } , true )
if err != nil && rc . Type == "DKIM" && strings . Contains ( err . Error ( ) , "alter read-only properties: fieldType" ) {
err = fmt . Errorf ( "This usually occurs when DKIM value is longer than the TXT record limit what OVH allows. Delete the TXT record to get past this limitation. [Original error: %s]" , err . Error ( ) )
}
return err
2017-11-11 03:02:34 +08:00
}
}
2019-05-21 10:31:59 +08:00
// Check if provided record is DKIM
func ( c * ovhProvider ) isDKIMRecord ( rc * models . RecordConfig ) bool {
return ( rc != nil && rc . Type == "TXT" && strings . Contains ( rc . GetLabel ( ) , "._domainkey" ) )
}
2017-11-11 03:02:34 +08:00
func ( c * ovhProvider ) refreshZone ( fqdn string ) error {
2019-05-21 03:39:19 +08:00
return c . client . CallAPI ( "POST" , fmt . Sprintf ( "/domain/zone/%s/refresh" , fqdn ) , nil , & Void { } , true )
2017-11-11 03:02:34 +08:00
}
// 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
}
2018-01-10 01:53:16 +08:00
// CurrentNameServer stores information about nameservers.
2017-11-11 03:02:34 +08:00
type CurrentNameServer struct {
ToDelete bool ` json:"toDelete,omitempty" `
2018-01-10 01:53:16 +08:00
IP string ` json:"ip,omitempty" `
2017-11-11 03:02:34 +08:00
IsUsed bool ` json:"isUsed,omitempty" `
2018-01-10 01:53:16 +08:00
ID int ` json:"id,omitempty" `
2017-11-11 03:02:34 +08:00
Host string ` json:"host,omitempty" `
}
// Retrieve the NS currently being deployed to the registrar
func ( c * ovhProvider ) fetchRegistrarNS ( fqdn string ) ( [ ] string , error ) {
2018-01-10 01:53:16 +08:00
var nameServersID [ ] int
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "GET" , "/domain/" + fqdn + "/nameServer" , nil , & nameServersID , true )
2017-11-11 03:02:34 +08:00
if err != nil {
return nil , err
}
var nameServers [ ] string
2018-01-10 01:53:16 +08:00
for _ , id := range nameServersID {
2017-11-11 03:02:34 +08:00
var ns CurrentNameServer
2019-05-21 03:39:19 +08:00
err = c . client . CallAPI ( "GET" , fmt . Sprintf ( "/domain/%s/nameServer/%d" , fqdn , id ) , nil , & ns , true )
2017-11-11 03:02:34 +08:00
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
}
2018-01-10 01:53:16 +08:00
// DomainNS describes a domain's NS in ovh's protocol.
2017-11-11 03:02:34 +08:00
type DomainNS struct {
Host string ` json:"host,omitempty" `
2018-01-10 01:53:16 +08:00
IP string ` json:"ip,omitempty" `
2017-11-11 03:02:34 +08:00
}
2018-01-10 01:53:16 +08:00
// UpdateNS describes a list of nameservers in ovh's protocol.
2017-11-11 03:02:34 +08:00
type UpdateNS struct {
NameServers [ ] DomainNS ` json:"nameServers" `
}
2018-01-10 01:53:16 +08:00
// Task describes a task in ovh's protocol.
2017-11-11 03:02:34 +08:00
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" `
2018-01-10 01:53:16 +08:00
ID int64 ` json:"id,omitempty" `
2017-11-11 03:02:34 +08:00
CanCancel bool ` json:"canCancel,omitempty" `
DoneDate string ` json:"doneDate,omitempty" `
CanRelaunch bool ` json:"canRelaunch,omitempty" `
}
2018-01-10 01:53:16 +08:00
// Domain describes a domain in ovh's protocol.
2017-11-11 03:02:34 +08:00
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" }
2019-05-21 03:39:19 +08:00
err := c . client . CallAPI ( "PUT" , fmt . Sprintf ( "/domain/%s" , fqdn ) , & domain , & Void { } , true )
2017-11-11 03:02:34 +08:00
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
2019-05-21 03:39:19 +08:00
err = c . client . CallAPI ( "POST" , fmt . Sprintf ( "/domain/%s/nameServers/update" , fqdn ) , & update , & task , true )
2017-11-11 03:02:34 +08:00
if err != nil {
return err
}
if task . Status == "error" {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-29 00:06:56 +08:00
return fmt . Errorf ( "API error while updating ns for %s: %s" , fqdn , task . Comment )
2017-11-11 03:02:34 +08:00
}
// 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
}