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 }