github.com/pmoroney/dnscontrol@v0.2.4-0.20171024134423-fad98f73f44a/providers/gandi/gandiProvider.go (about)

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