github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/ovh/protocol.go (about)

     1  package ovh
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/StackExchange/dnscontrol/v2/models"
     8  	"github.com/miekg/dns/dnsutil"
     9  )
    10  
    11  // Void an empty structure.
    12  type Void struct {
    13  }
    14  
    15  // fetchDomainList gets list of zones for account
    16  func (c *ovhProvider) fetchZones() error {
    17  	if c.zones != nil {
    18  		return nil
    19  	}
    20  	c.zones = map[string]bool{}
    21  
    22  	var response []string
    23  
    24  	err := c.client.CallAPI("GET", "/domain/zone", nil, &response, true)
    25  
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	for _, d := range response {
    31  		c.zones[d] = true
    32  	}
    33  	return nil
    34  }
    35  
    36  // Zone describes the attributes of a DNS zone.
    37  type Zone struct {
    38  	DNSSecSupported bool     `json:"dnssecSupported"`
    39  	HasDNSAnycast   bool     `json:"hasDNSAnycast,omitempty"`
    40  	NameServers     []string `json:"nameServers"`
    41  	LastUpdate      string   `json:"lastUpdate,omitempty"`
    42  }
    43  
    44  // get info about a zone.
    45  func (c *ovhProvider) fetchZone(fqdn string) (*Zone, error) {
    46  	var response Zone
    47  
    48  	err := c.client.CallAPI("GET", "/domain/zone/"+fqdn, nil, &response, true)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	return &response, nil
    54  }
    55  
    56  // Record describes a DNS record.
    57  type Record struct {
    58  	Target    string `json:"target,omitempty"`
    59  	Zone      string `json:"zone,omitempty"`
    60  	TTL       uint32 `json:"ttl,omitempty"`
    61  	FieldType string `json:"fieldType,omitempty"`
    62  	ID        int64  `json:"id,omitempty"`
    63  	SubDomain string `json:"subDomain,omitempty"`
    64  }
    65  
    66  type records struct {
    67  	recordsID []int
    68  }
    69  
    70  func (c *ovhProvider) fetchRecords(fqdn string) ([]*Record, error) {
    71  	var recordIds []int
    72  
    73  	err := c.client.CallAPI("GET", "/domain/zone/"+fqdn+"/record", nil, &recordIds, true)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	records := make([]*Record, len(recordIds))
    79  	for i, id := range recordIds {
    80  		r, err := c.fetchRecord(fqdn, id)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		records[i] = r
    85  	}
    86  
    87  	return records, nil
    88  }
    89  
    90  func (c *ovhProvider) fetchRecord(fqdn string, id int) (*Record, error) {
    91  	var response Record
    92  
    93  	err := c.client.CallAPI("GET", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, &response, true)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return &response, nil
    98  }
    99  
   100  // Returns a function that can be invoked to delete a record in a zone.
   101  func (c *ovhProvider) deleteRecordFunc(id int64, fqdn string) func() error {
   102  	return func() error {
   103  		err := c.client.CallAPI("DELETE", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, id), nil, nil, true)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		return nil
   108  	}
   109  }
   110  
   111  // Returns a function that can be invoked to create a record in a zone.
   112  func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) func() error {
   113  	return func() error {
   114  		if c.isDKIMRecord(rc) {
   115  			rc.Type = "DKIM"
   116  		}
   117  		record := Record{
   118  			SubDomain: dnsutil.TrimDomainName(rc.GetLabelFQDN(), fqdn),
   119  			FieldType: rc.Type,
   120  			Target:    rc.GetTargetCombined(),
   121  			TTL:       rc.TTL,
   122  		}
   123  		if record.SubDomain == "@" {
   124  			record.SubDomain = ""
   125  		}
   126  		var response Record
   127  		err := c.client.CallAPI("POST", fmt.Sprintf("/domain/zone/%s/record", fqdn), &record, &response, true)
   128  		return err
   129  	}
   130  }
   131  
   132  // Returns a function that can be invoked to update a record in a zone.
   133  func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqdn string) func() error {
   134  	return func() error {
   135  		if c.isDKIMRecord(rc) {
   136  			rc.Type = "DKIM"
   137  		}
   138  		record := Record{
   139  			SubDomain: rc.GetLabel(),
   140  			FieldType: rc.Type,
   141  			Target:    rc.GetTargetCombined(),
   142  			TTL:       rc.TTL,
   143  			Zone:      fqdn,
   144  			ID:        old.ID,
   145  		}
   146  		if record.SubDomain == "@" {
   147  			record.SubDomain = ""
   148  		}
   149  
   150  		err := c.client.CallAPI("PUT", fmt.Sprintf("/domain/zone/%s/record/%d", fqdn, old.ID), &record, &Void{}, true)
   151  		if err != nil && rc.Type == "DKIM" && strings.Contains(err.Error(), "alter read-only properties: fieldType") {
   152  			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())
   153  		}
   154  
   155  		return err
   156  	}
   157  }
   158  
   159  // Check if provided record is DKIM
   160  func (c *ovhProvider) isDKIMRecord(rc *models.RecordConfig) bool {
   161  	return (rc != nil && rc.Type == "TXT" && strings.Contains(rc.GetLabel(), "._domainkey"))
   162  }
   163  
   164  func (c *ovhProvider) refreshZone(fqdn string) error {
   165  	return c.client.CallAPI("POST", fmt.Sprintf("/domain/zone/%s/refresh", fqdn), nil, &Void{}, true)
   166  }
   167  
   168  // fetch the NS OVH attributed to this zone (which is distinct from fetchRealNS which
   169  // get the exact NS stored at the registrar
   170  func (c *ovhProvider) fetchNS(fqdn string) ([]string, error) {
   171  	zone, err := c.fetchZone(fqdn)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	return zone.NameServers, nil
   177  }
   178  
   179  // CurrentNameServer stores information about nameservers.
   180  type CurrentNameServer struct {
   181  	ToDelete bool   `json:"toDelete,omitempty"`
   182  	IP       string `json:"ip,omitempty"`
   183  	IsUsed   bool   `json:"isUsed,omitempty"`
   184  	ID       int    `json:"id,omitempty"`
   185  	Host     string `json:"host,omitempty"`
   186  }
   187  
   188  // Retrieve the NS currently being deployed to the registrar
   189  func (c *ovhProvider) fetchRegistrarNS(fqdn string) ([]string, error) {
   190  	var nameServersID []int
   191  	err := c.client.CallAPI("GET", "/domain/"+fqdn+"/nameServer", nil, &nameServersID, true)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	var nameServers []string
   197  	for _, id := range nameServersID {
   198  		var ns CurrentNameServer
   199  		err = c.client.CallAPI("GET", fmt.Sprintf("/domain/%s/nameServer/%d", fqdn, id), nil, &ns, true)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  
   204  		// skip NS that we asked for deletion
   205  		if ns.ToDelete {
   206  			continue
   207  		}
   208  		nameServers = append(nameServers, ns.Host)
   209  	}
   210  
   211  	return nameServers, nil
   212  }
   213  
   214  // DomainNS describes a domain's NS in ovh's protocol.
   215  type DomainNS struct {
   216  	Host string `json:"host,omitempty"`
   217  	IP   string `json:"ip,omitempty"`
   218  }
   219  
   220  // UpdateNS describes a list of nameservers in ovh's protocol.
   221  type UpdateNS struct {
   222  	NameServers []DomainNS `json:"nameServers"`
   223  }
   224  
   225  // Task describes a task in ovh's protocol.
   226  type Task struct {
   227  	Function      string `json:"function,omitempty"`
   228  	Status        string `json:"status,omitempty"`
   229  	CanAccelerate bool   `json:"canAccelerate,omitempty"`
   230  	LastUpdate    string `json:"lastUpdate,omitempty"`
   231  	CreationDate  string `json:"creationDate,omitempty"`
   232  	Comment       string `json:"comment,omitempty"`
   233  	TodoDate      string `json:"todoDate,omitempty"`
   234  	ID            int64  `json:"id,omitempty"`
   235  	CanCancel     bool   `json:"canCancel,omitempty"`
   236  	DoneDate      string `json:"doneDate,omitempty"`
   237  	CanRelaunch   bool   `json:"canRelaunch,omitempty"`
   238  }
   239  
   240  // Domain describes a domain in ovh's protocol.
   241  type Domain struct {
   242  	NameServerType     string `json:"nameServerType,omitempty"`
   243  	TransferLockStatus string `json:"transferLockStatus,omitempty"`
   244  }
   245  
   246  func (c *ovhProvider) updateNS(fqdn string, ns []string) error {
   247  	// we first need to make sure we can edit the NS
   248  	// by default zones are in "hosted" mode meaning they default
   249  	// to OVH default NS. In this mode, the NS can't be updated.
   250  	domain := Domain{NameServerType: "external"}
   251  	err := c.client.CallAPI("PUT", fmt.Sprintf("/domain/%s", fqdn), &domain, &Void{}, true)
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	var newNs []DomainNS
   257  	for _, n := range ns {
   258  		newNs = append(newNs, DomainNS{
   259  			Host: n,
   260  		})
   261  	}
   262  
   263  	update := UpdateNS{
   264  		NameServers: newNs,
   265  	}
   266  	var task Task
   267  	err = c.client.CallAPI("POST", fmt.Sprintf("/domain/%s/nameServers/update", fqdn), &update, &task, true)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	if task.Status == "error" {
   273  		return fmt.Errorf("API error while updating ns for %s: %s", fqdn, task.Comment)
   274  	}
   275  
   276  	// we don't wait for the task execution. One of the reason is that
   277  	// NS modification can take time in the registrar, the other is that every task
   278  	// in OVH is usually executed a few minutes after they have been registered.
   279  	// We count on the fact that `GetNameservers` uses the registrar API to get
   280  	// a coherent view (including pending modifications) of the registered NS.
   281  
   282  	return nil
   283  }