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

     1  package ns1
     2  
     3  import (
     4  	"encoding/json"
     5  
     6  	"fmt"
     7  
     8  	"github.com/StackExchange/dnscontrol/models"
     9  	"github.com/StackExchange/dnscontrol/providers"
    10  
    11  	"net/http"
    12  
    13  	"strings"
    14  
    15  	"github.com/StackExchange/dnscontrol/providers/diff"
    16  	"github.com/miekg/dns/dnsutil"
    17  	"gopkg.in/ns1/ns1-go.v2/rest"
    18  	"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
    19  )
    20  
    21  var docNotes = providers.DocumentationNotes{
    22  	providers.DocCreateDomains:       providers.Cannot(),
    23  	providers.DocOfficiallySupported: providers.Cannot(),
    24  	providers.DocDualHost:            providers.Can(),
    25  }
    26  
    27  func init() {
    28  	providers.RegisterDomainServiceProviderType("NS1", newProvider, docNotes)
    29  }
    30  
    31  type nsone struct {
    32  	*rest.Client
    33  }
    34  
    35  func newProvider(creds map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) {
    36  	if creds["api_token"] == "" {
    37  		return nil, fmt.Errorf("api_token required for ns1")
    38  	}
    39  	return &nsone{rest.NewClient(http.DefaultClient, rest.SetAPIKey(creds["api_token"]))}, nil
    40  }
    41  
    42  func (n *nsone) GetNameservers(domain string) ([]*models.Nameserver, error) {
    43  	z, _, err := n.Zones.Get(domain)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	return models.StringsToNameservers(z.DNSServers), nil
    48  }
    49  
    50  func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
    51  	dc.Punycode()
    52  	dc.CombineMXs()
    53  	z, _, err := n.Zones.Get(dc.Name)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	found := models.Records{}
    59  	for _, r := range z.Records {
    60  		zrs, err := convert(r, dc.Name)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  		found = append(found, zrs...)
    65  	}
    66  	foundGrouped := found.Grouped()
    67  	desiredGrouped := dc.Records.Grouped()
    68  
    69  	differ := diff.New(dc)
    70  	changedGroups := differ.ChangedGroups(found)
    71  	corrections := []*models.Correction{}
    72  	// each name/type is given to the api as a unit.
    73  	for k, descs := range changedGroups {
    74  		key := k
    75  		desc := strings.Join(descs, "\n")
    76  		_, current := foundGrouped[k]
    77  		recs, wanted := desiredGrouped[k]
    78  		if wanted && !current {
    79  			// pure addition
    80  			corrections = append(corrections, &models.Correction{
    81  				Msg: desc,
    82  				F:   func() error { return n.add(recs, dc.Name) },
    83  			})
    84  		} else if current && !wanted {
    85  			// pure deletion
    86  			corrections = append(corrections, &models.Correction{
    87  				Msg: desc,
    88  				F:   func() error { return n.remove(key, dc.Name) },
    89  			})
    90  		} else {
    91  			// modification
    92  			corrections = append(corrections, &models.Correction{
    93  				Msg: desc,
    94  				F:   func() error { return n.modify(recs, dc.Name) },
    95  			})
    96  		}
    97  	}
    98  	return corrections, nil
    99  }
   100  
   101  func (n *nsone) add(recs models.Records, domain string) error {
   102  	_, err := n.Records.Create(buildRecord(recs, domain, ""))
   103  	return err
   104  }
   105  
   106  func (n *nsone) remove(key models.RecordKey, domain string) error {
   107  	_, err := n.Records.Delete(domain, dnsutil.AddOrigin(key.Name, domain), key.Type)
   108  	return err
   109  }
   110  
   111  func (n *nsone) modify(recs models.Records, domain string) error {
   112  	_, err := n.Records.Update(buildRecord(recs, domain, ""))
   113  	return err
   114  }
   115  
   116  func buildRecord(recs models.Records, domain string, id string) *dns.Record {
   117  	r := recs[0]
   118  	rec := &dns.Record{
   119  		Domain: r.NameFQDN,
   120  		Type:   r.Type,
   121  		ID:     id,
   122  		TTL:    int(r.TTL),
   123  		Zone:   domain,
   124  	}
   125  	for _, r := range recs {
   126  		ans := &dns.Answer{
   127  			Rdata: strings.Split(r.Target, " "),
   128  		}
   129  		rec.AddAnswer(ans)
   130  	}
   131  	return rec
   132  }
   133  
   134  func convert(zr *dns.ZoneRecord, domain string) ([]*models.RecordConfig, error) {
   135  	found := []*models.RecordConfig{}
   136  	for _, ans := range zr.ShortAns {
   137  		rec := &models.RecordConfig{
   138  			NameFQDN: zr.Domain,
   139  			Name:     dnsutil.TrimDomainName(zr.Domain, domain),
   140  			TTL:      uint32(zr.TTL),
   141  			Target:   ans,
   142  			Original: zr,
   143  			Type:     zr.Type,
   144  		}
   145  		if zr.Type == "MX" {
   146  			rec.CombinedTarget = true
   147  		}
   148  		found = append(found, rec)
   149  	}
   150  	return found, nil
   151  }