github.com/stackexchange/dnscontrol@v0.2.8/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 "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, providers.CanUseSRV, 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, errors.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 // Normalize 70 models.PostProcessRecords(found) 71 72 differ := diff.New(dc) 73 changedGroups := differ.ChangedGroups(found) 74 corrections := []*models.Correction{} 75 // each name/type is given to the api as a unit. 76 for k, descs := range changedGroups { 77 key := k 78 desc := strings.Join(descs, "\n") 79 _, current := foundGrouped[k] 80 recs, wanted := desiredGrouped[k] 81 if wanted && !current { 82 // pure addition 83 corrections = append(corrections, &models.Correction{ 84 Msg: desc, 85 F: func() error { return n.add(recs, dc.Name) }, 86 }) 87 } else if current && !wanted { 88 // pure deletion 89 corrections = append(corrections, &models.Correction{ 90 Msg: desc, 91 F: func() error { return n.remove(key, dc.Name) }, 92 }) 93 } else { 94 // modification 95 corrections = append(corrections, &models.Correction{ 96 Msg: desc, 97 F: func() error { return n.modify(recs, dc.Name) }, 98 }) 99 } 100 } 101 return corrections, nil 102 } 103 104 func (n *nsone) add(recs models.Records, domain string) error { 105 _, err := n.Records.Create(buildRecord(recs, domain, "")) 106 return err 107 } 108 109 func (n *nsone) remove(key models.RecordKey, domain string) error { 110 _, err := n.Records.Delete(domain, key.NameFQDN, key.Type) 111 return err 112 } 113 114 func (n *nsone) modify(recs models.Records, domain string) error { 115 _, err := n.Records.Update(buildRecord(recs, domain, "")) 116 return err 117 } 118 119 func buildRecord(recs models.Records, domain string, id string) *dns.Record { 120 r := recs[0] 121 rec := &dns.Record{ 122 Domain: r.GetLabelFQDN(), 123 Type: r.Type, 124 ID: id, 125 TTL: int(r.TTL), 126 Zone: domain, 127 } 128 for _, r := range recs { 129 if r.Type == "TXT" { 130 rec.AddAnswer(&dns.Answer{Rdata: r.TxtStrings}) 131 } else if r.Type == "SRV" { 132 rec.AddAnswer(&dns.Answer{Rdata: strings.Split(fmt.Sprintf("%d %d %d %v", r.SrvPriority, r.SrvWeight, r.SrvPort, r.GetTargetField()), " ")}) 133 } else { 134 rec.AddAnswer(&dns.Answer{Rdata: strings.Split(r.GetTargetField(), " ")}) 135 } 136 } 137 return rec 138 } 139 140 func convert(zr *dns.ZoneRecord, domain string) ([]*models.RecordConfig, error) { 141 found := []*models.RecordConfig{} 142 for _, ans := range zr.ShortAns { 143 rec := &models.RecordConfig{ 144 TTL: uint32(zr.TTL), 145 Original: zr, 146 } 147 rec.SetLabelFromFQDN(zr.Domain, domain) 148 switch rtype := zr.Type; rtype { 149 default: 150 if err := rec.PopulateFromString(rtype, ans, domain); err != nil { 151 panic(errors.Wrap(err, "unparsable record received from ns1")) 152 } 153 } 154 found = append(found, rec) 155 } 156 return found, nil 157 }