github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/ns1/ns1provider.go (about)

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