github.com/stackexchange/dnscontrol@v0.2.8/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  	"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, providers.CanUseSRV, 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, errors.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  	//  Normalize
    70  	models.PostProcessRecords(found)
    71  
    72  	differ := diff.New(dc)
    73  	changedGroups := differ.ChangedGroups(found)
    74  	corrections := []*models.Correction{}
    75  	// each name/type is given to the api as a unit.
    76  	for k, descs := range changedGroups {
    77  		key := k
    78  		desc := strings.Join(descs, "\n")
    79  		_, current := foundGrouped[k]
    80  		recs, wanted := desiredGrouped[k]
    81  		if wanted && !current {
    82  			// pure addition
    83  			corrections = append(corrections, &models.Correction{
    84  				Msg: desc,
    85  				F:   func() error { return n.add(recs, dc.Name) },
    86  			})
    87  		} else if current && !wanted {
    88  			// pure deletion
    89  			corrections = append(corrections, &models.Correction{
    90  				Msg: desc,
    91  				F:   func() error { return n.remove(key, dc.Name) },
    92  			})
    93  		} else {
    94  			// modification
    95  			corrections = append(corrections, &models.Correction{
    96  				Msg: desc,
    97  				F:   func() error { return n.modify(recs, dc.Name) },
    98  			})
    99  		}
   100  	}
   101  	return corrections, nil
   102  }
   103  
   104  func (n *nsone) add(recs models.Records, domain string) error {
   105  	_, err := n.Records.Create(buildRecord(recs, domain, ""))
   106  	return err
   107  }
   108  
   109  func (n *nsone) remove(key models.RecordKey, domain string) error {
   110  	_, err := n.Records.Delete(domain, key.NameFQDN, key.Type)
   111  	return err
   112  }
   113  
   114  func (n *nsone) modify(recs models.Records, domain string) error {
   115  	_, err := n.Records.Update(buildRecord(recs, domain, ""))
   116  	return err
   117  }
   118  
   119  func buildRecord(recs models.Records, domain string, id string) *dns.Record {
   120  	r := recs[0]
   121  	rec := &dns.Record{
   122  		Domain: r.GetLabelFQDN(),
   123  		Type:   r.Type,
   124  		ID:     id,
   125  		TTL:    int(r.TTL),
   126  		Zone:   domain,
   127  	}
   128  	for _, r := range recs {
   129  		if r.Type == "TXT" {
   130  			rec.AddAnswer(&dns.Answer{Rdata: r.TxtStrings})
   131  		} else if r.Type == "SRV" {
   132  			rec.AddAnswer(&dns.Answer{Rdata: strings.Split(fmt.Sprintf("%d %d %d %v", r.SrvPriority, r.SrvWeight, r.SrvPort, r.GetTargetField()), " ")})
   133  		} else {
   134  			rec.AddAnswer(&dns.Answer{Rdata: strings.Split(r.GetTargetField(), " ")})
   135  		}
   136  	}
   137  	return rec
   138  }
   139  
   140  func convert(zr *dns.ZoneRecord, domain string) ([]*models.RecordConfig, error) {
   141  	found := []*models.RecordConfig{}
   142  	for _, ans := range zr.ShortAns {
   143  		rec := &models.RecordConfig{
   144  			TTL:      uint32(zr.TTL),
   145  			Original: zr,
   146  		}
   147  		rec.SetLabelFromFQDN(zr.Domain, domain)
   148  		switch rtype := zr.Type; rtype {
   149  		default:
   150  			if err := rec.PopulateFromString(rtype, ans, domain); err != nil {
   151  				panic(errors.Wrap(err, "unparsable record received from ns1"))
   152  			}
   153  		}
   154  		found = append(found, rec)
   155  	}
   156  	return found, nil
   157  }