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