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

     1  package namedotcom
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/miekg/dns/dnsutil"
     9  
    10  	"github.com/StackExchange/dnscontrol/models"
    11  	"github.com/StackExchange/dnscontrol/providers/diff"
    12  )
    13  
    14  var defaultNameservers = []*models.Nameserver{
    15  	{Name: "ns1.name.com"},
    16  	{Name: "ns2.name.com"},
    17  	{Name: "ns3.name.com"},
    18  	{Name: "ns4.name.com"},
    19  }
    20  
    21  func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
    22  	dc.Punycode()
    23  	records, err := n.getRecords(dc.Name)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	actual := make([]*models.RecordConfig, len(records))
    28  	for i, r := range records {
    29  		actual[i] = r.toRecord()
    30  	}
    31  
    32  	for _, rec := range dc.Records {
    33  		if rec.Type == "ALIAS" {
    34  			rec.Type = "ANAME"
    35  		}
    36  	}
    37  
    38  	checkNSModifications(dc)
    39  
    40  	differ := diff.New(dc)
    41  	_, create, del, mod := differ.IncrementalDiff(actual)
    42  	corrections := []*models.Correction{}
    43  
    44  	for _, d := range del {
    45  		rec := d.Existing.Original.(*nameComRecord)
    46  		c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.RecordID, dc.Name) }}
    47  		corrections = append(corrections, c)
    48  	}
    49  	for _, cre := range create {
    50  		rec := cre.Desired
    51  		c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
    52  		corrections = append(corrections, c)
    53  	}
    54  	for _, chng := range mod {
    55  		old := chng.Existing.Original.(*nameComRecord)
    56  		new := chng.Desired
    57  		c := &models.Correction{Msg: chng.String(), F: func() error {
    58  			err := n.deleteRecord(old.RecordID, dc.Name)
    59  			if err != nil {
    60  				return err
    61  			}
    62  			return n.createRecord(new, dc.Name)
    63  		}}
    64  		corrections = append(corrections, c)
    65  	}
    66  	return corrections, nil
    67  }
    68  
    69  func (n *nameDotCom) apiGetRecords(domain string) string {
    70  	return fmt.Sprintf("%s/dns/list/%s", n.APIUrl, domain)
    71  }
    72  func (n *nameDotCom) apiCreateRecord(domain string) string {
    73  	return fmt.Sprintf("%s/dns/create/%s", n.APIUrl, domain)
    74  }
    75  func (n *nameDotCom) apiDeleteRecord(domain string) string {
    76  	return fmt.Sprintf("%s/dns/delete/%s", n.APIUrl, domain)
    77  }
    78  
    79  type nameComRecord struct {
    80  	RecordID string `json:"record_id"`
    81  	Name     string `json:"name"`
    82  	Type     string `json:"type"`
    83  	Content  string `json:"content"`
    84  	TTL      string `json:"ttl"`
    85  	Priority string `json:"priority"`
    86  }
    87  
    88  func checkNSModifications(dc *models.DomainConfig) {
    89  	newList := make([]*models.RecordConfig, 0, len(dc.Records))
    90  	for _, rec := range dc.Records {
    91  		if rec.Type == "NS" && rec.NameFQDN == dc.Name {
    92  			continue // Apex NS records are automatically created for the domain's nameservers and cannot be managed otherwise via the name.com API.
    93  		}
    94  		newList = append(newList, rec)
    95  	}
    96  	dc.Records = newList
    97  }
    98  
    99  func (r *nameComRecord) toRecord() *models.RecordConfig {
   100  	ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
   101  	prio, _ := strconv.ParseUint(r.Priority, 10, 16)
   102  	rc := &models.RecordConfig{
   103  		NameFQDN: r.Name,
   104  		Type:     r.Type,
   105  		Target:   r.Content,
   106  		TTL:      uint32(ttl),
   107  		Original: r,
   108  	}
   109  	switch r.Type { // #rtype_variations
   110  	case "A", "AAAA", "ANAME", "CNAME", "NS", "TXT":
   111  		// nothing additional.
   112  	case "MX":
   113  		rc.MxPreference = uint16(prio)
   114  	case "SRV":
   115  		parts := strings.Split(r.Content, " ")
   116  		weight, _ := strconv.ParseInt(parts[0], 10, 32)
   117  		port, _ := strconv.ParseInt(parts[1], 10, 32)
   118  		rc.SrvWeight = uint16(weight)
   119  		rc.SrvPort = uint16(port)
   120  		rc.SrvPriority = uint16(prio)
   121  		rc.MxPreference = 0
   122  		rc.Target = parts[2] + "."
   123  	default:
   124  		panic(fmt.Sprintf("toRecord unimplemented rtype %v", r.Type))
   125  		// We panic so that we quickly find any switch statements
   126  		// that have not been updated for a new RR type.
   127  	}
   128  	return rc
   129  }
   130  
   131  type listRecordsResponse struct {
   132  	*apiResult
   133  	Records []*nameComRecord `json:"records"`
   134  }
   135  
   136  func (n *nameDotCom) getRecords(domain string) ([]*nameComRecord, error) {
   137  	result := &listRecordsResponse{}
   138  	err := n.get(n.apiGetRecords(domain), result)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	if err = result.getErr(); err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	for _, rc := range result.Records {
   147  		if rc.Type == "CNAME" || rc.Type == "ANAME" || rc.Type == "MX" || rc.Type == "NS" {
   148  			rc.Content = rc.Content + "."
   149  		}
   150  	}
   151  	return result.Records, nil
   152  }
   153  
   154  func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error {
   155  	target := rc.Target
   156  	if rc.Type == "CNAME" || rc.Type == "ANAME" || rc.Type == "MX" || rc.Type == "NS" {
   157  		if target[len(target)-1] == '.' {
   158  			target = target[:len(target)-1]
   159  		} else {
   160  			return fmt.Errorf("Unexpected. CNAME/MX/NS target did not end with dot.\n")
   161  		}
   162  	}
   163  	dat := struct {
   164  		Hostname string `json:"hostname"`
   165  		Type     string `json:"type"`
   166  		Content  string `json:"content"`
   167  		TTL      uint32 `json:"ttl,omitempty"`
   168  		Priority uint16 `json:"priority,omitempty"`
   169  	}{
   170  		Hostname: dnsutil.TrimDomainName(rc.NameFQDN, domain),
   171  		Type:     rc.Type,
   172  		Content:  target,
   173  		TTL:      rc.TTL,
   174  		Priority: rc.MxPreference,
   175  	}
   176  	if dat.Hostname == "@" {
   177  		dat.Hostname = ""
   178  	}
   179  	switch rc.Type { // #rtype_variations
   180  	case "A", "AAAA", "ANAME", "CNAME", "MX", "NS", "TXT":
   181  		// nothing
   182  	case "SRV":
   183  		dat.Content = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.Target)
   184  		dat.Priority = rc.SrvPriority
   185  	default:
   186  		panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type))
   187  		// We panic so that we quickly find any switch statements
   188  		// that have not been updated for a new RR type.
   189  	}
   190  	resp, err := n.post(n.apiCreateRecord(domain), dat)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	return resp.getErr()
   195  }
   196  
   197  func (n *nameDotCom) deleteRecord(id, domain string) error {
   198  	dat := struct {
   199  		ID string `json:"record_id"`
   200  	}{id}
   201  	resp, err := n.post(n.apiDeleteRecord(domain), dat)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return resp.getErr()
   206  }