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  }