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