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  }