github.com/kurthockenbury/dnscontrol@v0.2.8/providers/gandi/gandiProvider.go (about)

     1  package gandi
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/StackExchange/dnscontrol/models"
     9  	"github.com/StackExchange/dnscontrol/pkg/printer"
    10  	"github.com/StackExchange/dnscontrol/providers"
    11  	"github.com/StackExchange/dnscontrol/providers/diff"
    12  	"github.com/pkg/errors"
    13  
    14  	"strings"
    15  
    16  	gandidomain "github.com/prasmussen/gandi-api/domain"
    17  	gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
    18  )
    19  
    20  /*
    21  
    22  Gandi API DNS provider:
    23  
    24  Info required in `creds.json`:
    25     - apikey
    26  
    27  */
    28  
    29  var features = providers.DocumentationNotes{
    30  	providers.CanUseCAA:              providers.Can(),
    31  	providers.CanUsePTR:              providers.Can(),
    32  	providers.CanUseSRV:              providers.Can(),
    33  	providers.CantUseNOPURGE:         providers.Cannot(),
    34  	providers.DocCreateDomains:       providers.Cannot("Can only manage domains registered through their service"),
    35  	providers.DocOfficiallySupported: providers.Cannot(),
    36  }
    37  
    38  func init() {
    39  	providers.RegisterDomainServiceProviderType("GANDI", newDsp, features)
    40  	providers.RegisterRegistrarType("GANDI", newReg)
    41  }
    42  
    43  // GandiApi is the API handle for this module.
    44  type GandiApi struct {
    45  	ApiKey      string
    46  	domainIndex map[string]int64 // Map of domainname to index
    47  	nameservers map[string][]*models.Nameserver
    48  	ZoneId      int64
    49  }
    50  
    51  type gandiRecord struct {
    52  	gandirecord.RecordInfo
    53  }
    54  
    55  func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) {
    56  	if err := c.fetchDomainList(); err != nil {
    57  		return nil, err
    58  	}
    59  	_, ok := c.domainIndex[domain]
    60  	if !ok {
    61  		return nil, errors.Errorf("%s not listed in zones for gandi account", domain)
    62  	}
    63  	return c.fetchDomainInfo(domain)
    64  }
    65  
    66  // GetNameservers returns the nameservers for domain.
    67  func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
    68  	domaininfo, err := c.getDomainInfo(domain)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	ns := []*models.Nameserver{}
    73  	for _, nsname := range domaininfo.Nameservers {
    74  		ns = append(ns, &models.Nameserver{Name: nsname})
    75  	}
    76  	return ns, nil
    77  }
    78  
    79  // GetDomainCorrections returns a list of corrections recommended for this domain.
    80  func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
    81  	dc.Punycode()
    82  	domaininfo, err := c.getDomainInfo(dc.Name)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	foundRecords, err := c.getZoneRecords(domaininfo.ZoneId, dc.Name)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	expectedRecordSets := make([]gandirecord.RecordSet, 0, len(dc.Records))
    92  	recordsToKeep := make([]*models.RecordConfig, 0, len(dc.Records))
    93  	for _, rec := range dc.Records {
    94  		if rec.TTL < 300 {
    95  			printer.Warnf("Gandi does not support ttls < 300. Setting %s from %d to 300\n", rec.GetLabelFQDN(), rec.TTL)
    96  			rec.TTL = 300
    97  		}
    98  		if rec.TTL > 2592000 {
    99  			return nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL)
   100  		}
   101  		if rec.Type == "TXT" {
   102  			rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting.
   103  		}
   104  		if rec.Type == "NS" && rec.GetLabel() == "@" {
   105  			if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
   106  				printer.Warnf("Gandi does not support changing apex NS records. %s will not be added.\n", rec.GetTargetField())
   107  			}
   108  			continue
   109  		}
   110  		rs := gandirecord.RecordSet{
   111  			"type":  rec.Type,
   112  			"name":  rec.GetLabel(),
   113  			"value": rec.GetTargetCombined(),
   114  			"ttl":   rec.TTL,
   115  		}
   116  		expectedRecordSets = append(expectedRecordSets, rs)
   117  		recordsToKeep = append(recordsToKeep, rec)
   118  	}
   119  	dc.Records = recordsToKeep
   120  
   121  	// Normalize
   122  	models.PostProcessRecords(foundRecords)
   123  
   124  	differ := diff.New(dc)
   125  	_, create, del, mod := differ.IncrementalDiff(foundRecords)
   126  
   127  	// Print a list of changes. Generate an actual change that is the zone
   128  	changes := false
   129  	desc := ""
   130  	for _, i := range create {
   131  		changes = true
   132  		desc += "\n" + i.String()
   133  	}
   134  	for _, i := range del {
   135  		changes = true
   136  		desc += "\n" + i.String()
   137  	}
   138  	for _, i := range mod {
   139  		changes = true
   140  		desc += "\n" + i.String()
   141  	}
   142  
   143  	msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
   144  	corrections := []*models.Correction{}
   145  	if changes {
   146  		corrections = append(corrections,
   147  			&models.Correction{
   148  				Msg: msg,
   149  				F: func() error {
   150  					printer.Printf("CREATING ZONE: %v\n", dc.Name)
   151  					return c.createGandiZone(dc.Name, domaininfo.ZoneId, expectedRecordSets)
   152  				},
   153  			})
   154  	}
   155  
   156  	return corrections, nil
   157  }
   158  
   159  func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
   160  	return newGandi(conf, metadata)
   161  }
   162  
   163  func newReg(conf map[string]string) (providers.Registrar, error) {
   164  	return newGandi(conf, nil)
   165  }
   166  
   167  func newGandi(m map[string]string, metadata json.RawMessage) (*GandiApi, error) {
   168  	api := &GandiApi{}
   169  	api.ApiKey = m["apikey"]
   170  	if api.ApiKey == "" {
   171  		return nil, errors.Errorf("missing Gandi apikey")
   172  	}
   173  
   174  	return api, nil
   175  }
   176  
   177  // GetRegistrarCorrections returns a list of corrections for this registrar.
   178  func (c *GandiApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
   179  	domaininfo, err := c.getDomainInfo(dc.Name)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	sort.Strings(domaininfo.Nameservers)
   184  	found := strings.Join(domaininfo.Nameservers, ",")
   185  	desiredNs := []string{}
   186  	for _, d := range dc.Nameservers {
   187  		desiredNs = append(desiredNs, d.Name)
   188  	}
   189  	sort.Strings(desiredNs)
   190  	desired := strings.Join(desiredNs, ",")
   191  	if found != desired {
   192  		return []*models.Correction{
   193  			{
   194  				Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired),
   195  				F: func() (err error) {
   196  					_, err = c.setDomainNameservers(dc.Name, desiredNs)
   197  					return
   198  				}},
   199  		}, nil
   200  	}
   201  	return nil, nil
   202  }