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