github.com/karsthammer/dnscontrol@v0.2.8/providers/namedotcom/records.go (about) 1 package namedotcom 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/namedotcom/go/namecom" 9 "github.com/pkg/errors" 10 11 "github.com/StackExchange/dnscontrol/models" 12 "github.com/StackExchange/dnscontrol/providers/diff" 13 ) 14 15 var defaultNameservers = []*models.Nameserver{ 16 {Name: "ns1.name.com"}, 17 {Name: "ns2.name.com"}, 18 {Name: "ns3.name.com"}, 19 {Name: "ns4.name.com"}, 20 } 21 22 // GetDomainCorrections gathers correctios that would bring n to match dc. 23 func (n *NameCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 24 dc.Punycode() 25 records, err := n.getRecords(dc.Name) 26 if err != nil { 27 return nil, err 28 } 29 actual := make([]*models.RecordConfig, len(records)) 30 for i, r := range records { 31 actual[i] = toRecord(r, dc.Name) 32 } 33 34 for _, rec := range dc.Records { 35 if rec.Type == "ALIAS" { 36 rec.Type = "ANAME" 37 } 38 } 39 40 checkNSModifications(dc) 41 42 // Normalize 43 models.PostProcessRecords(actual) 44 45 differ := diff.New(dc) 46 _, create, del, mod := differ.IncrementalDiff(actual) 47 corrections := []*models.Correction{} 48 49 for _, d := range del { 50 rec := d.Existing.Original.(*namecom.Record) 51 c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.ID, dc.Name) }} 52 corrections = append(corrections, c) 53 } 54 for _, cre := range create { 55 rec := cre.Desired 56 c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }} 57 corrections = append(corrections, c) 58 } 59 for _, chng := range mod { 60 old := chng.Existing.Original.(*namecom.Record) 61 new := chng.Desired 62 c := &models.Correction{Msg: chng.String(), F: func() error { 63 err := n.deleteRecord(old.ID, dc.Name) 64 if err != nil { 65 return err 66 } 67 return n.createRecord(new, dc.Name) 68 }} 69 corrections = append(corrections, c) 70 } 71 return corrections, nil 72 } 73 74 func checkNSModifications(dc *models.DomainConfig) { 75 newList := make([]*models.RecordConfig, 0, len(dc.Records)) 76 for _, rec := range dc.Records { 77 if rec.Type == "NS" && rec.GetLabel() == "@" { 78 continue // Apex NS records are automatically created for the domain's nameservers and cannot be managed otherwise via the name.com API. 79 } 80 newList = append(newList, rec) 81 } 82 dc.Records = newList 83 } 84 85 func toRecord(r *namecom.Record, origin string) *models.RecordConfig { 86 rc := &models.RecordConfig{ 87 Type: r.Type, 88 TTL: r.TTL, 89 Original: r, 90 } 91 if !strings.HasSuffix(r.Fqdn, ".") { 92 panic(errors.Errorf("namedotcom suddenly changed protocol. Bailing. (%v)", r.Fqdn)) 93 } 94 fqdn := r.Fqdn[:len(r.Fqdn)-1] 95 rc.SetLabelFromFQDN(fqdn, origin) 96 switch rtype := r.Type; rtype { // #rtype_variations 97 case "TXT": 98 rc.SetTargetTXTs(decodeTxt(r.Answer)) 99 case "MX": 100 if err := rc.SetTargetMX(uint16(r.Priority), r.Answer); err != nil { 101 panic(errors.Wrap(err, "unparsable MX record received from ndc")) 102 } 103 case "SRV": 104 if err := rc.SetTargetSRVPriorityString(uint16(r.Priority), r.Answer+"."); err != nil { 105 panic(errors.Wrap(err, "unparsable SRV record received from ndc")) 106 } 107 default: // "A", "AAAA", "ANAME", "CNAME", "NS" 108 if err := rc.PopulateFromString(rtype, r.Answer, r.Fqdn); err != nil { 109 panic(errors.Wrap(err, "unparsable record received from ndc")) 110 } 111 } 112 return rc 113 } 114 115 func (n *NameCom) getRecords(domain string) ([]*namecom.Record, error) { 116 var ( 117 err error 118 records []*namecom.Record 119 response *namecom.ListRecordsResponse 120 ) 121 122 request := &namecom.ListRecordsRequest{ 123 DomainName: domain, 124 Page: 1, 125 } 126 127 for request.Page > 0 { 128 response, err = n.client.ListRecords(request) 129 if err != nil { 130 return nil, err 131 } 132 133 records = append(records, response.Records...) 134 request.Page = response.NextPage 135 } 136 137 for _, rc := range records { 138 if rc.Type == "CNAME" || rc.Type == "ANAME" || rc.Type == "MX" || rc.Type == "NS" { 139 rc.Answer = rc.Answer + "." 140 } 141 } 142 return records, nil 143 } 144 145 func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error { 146 record := &namecom.Record{ 147 DomainName: domain, 148 Host: rc.GetLabel(), 149 Type: rc.Type, 150 Answer: rc.GetTargetField(), 151 TTL: rc.TTL, 152 Priority: uint32(rc.MxPreference), 153 } 154 switch rc.Type { // #rtype_variations 155 case "A", "AAAA", "ANAME", "CNAME", "MX", "NS": 156 // nothing 157 case "TXT": 158 record.Answer = encodeTxt(rc.TxtStrings) 159 case "SRV": 160 record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.GetTargetField()) 161 record.Priority = uint32(rc.SrvPriority) 162 default: 163 panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type)) 164 // We panic so that we quickly find any switch statements 165 // that have not been updated for a new RR type. 166 } 167 _, err := n.client.CreateRecord(record) 168 return err 169 } 170 171 // makeTxt encodes TxtStrings for sending in the CREATE/MODIFY API: 172 func encodeTxt(txts []string) string { 173 ans := txts[0] 174 175 if len(txts) > 1 { 176 ans = "" 177 for _, t := range txts { 178 ans += `"` + strings.Replace(t, `"`, `\"`, -1) + `"` 179 } 180 } 181 return ans 182 } 183 184 // finds a string surrounded by quotes that might contain an escaped quote charactor. 185 var quotedStringRegexp = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`) 186 187 // decodeTxt decodes the TXT record as received from name.com and 188 // returns the list of strings. 189 func decodeTxt(s string) []string { 190 191 if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { 192 txtStrings := []string{} 193 for _, t := range quotedStringRegexp.FindAllStringSubmatch(s, -1) { 194 txtString := strings.Replace(t[1], `\"`, `"`, -1) 195 txtStrings = append(txtStrings, txtString) 196 } 197 return txtStrings 198 } 199 return []string{s} 200 } 201 202 func (n *NameCom) deleteRecord(id int32, domain string) error { 203 request := &namecom.DeleteRecordRequest{ 204 DomainName: domain, 205 ID: id, 206 } 207 208 _, err := n.client.DeleteRecord(request) 209 return err 210 }