github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/providers/ns1/ns1provider.go (about) 1 package ns1 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "gopkg.in/ns1/ns1-go.v2/rest" 10 "gopkg.in/ns1/ns1-go.v2/rest/model/dns" 11 12 "github.com/StackExchange/dnscontrol/v2/models" 13 "github.com/StackExchange/dnscontrol/v2/providers" 14 "github.com/StackExchange/dnscontrol/v2/providers/diff" 15 ) 16 17 var docNotes = providers.DocumentationNotes{ 18 providers.DocCreateDomains: providers.Cannot(), 19 providers.DocOfficiallySupported: providers.Cannot(), 20 providers.DocDualHost: providers.Can(), 21 } 22 23 func init() { 24 providers.RegisterDomainServiceProviderType("NS1", newProvider, providers.CanUseSRV, docNotes) 25 } 26 27 type nsone struct { 28 *rest.Client 29 } 30 31 func newProvider(creds map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) { 32 if creds["api_token"] == "" { 33 return nil, fmt.Errorf("api_token required for ns1") 34 } 35 return &nsone{rest.NewClient(http.DefaultClient, rest.SetAPIKey(creds["api_token"]))}, nil 36 } 37 38 func (n *nsone) GetNameservers(domain string) ([]*models.Nameserver, error) { 39 z, _, err := n.Zones.Get(domain) 40 if err != nil { 41 return nil, err 42 } 43 return models.StringsToNameservers(z.DNSServers), nil 44 } 45 46 // GetZoneRecords gets the records of a zone and returns them in RecordConfig format. 47 func (client *nsone) GetZoneRecords(domain string) (models.Records, error) { 48 return nil, fmt.Errorf("not implemented") 49 // This enables the get-zones subcommand. 50 // Implement this by extracting the code from GetDomainCorrections into 51 // a single function. For most providers this should be relatively easy. 52 } 53 54 func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 55 dc.Punycode() 56 //dc.CombineMXs() 57 z, _, err := n.Zones.Get(dc.Name) 58 if err != nil { 59 return nil, err 60 } 61 62 found := models.Records{} 63 for _, r := range z.Records { 64 zrs, err := convert(r, dc.Name) 65 if err != nil { 66 return nil, err 67 } 68 found = append(found, zrs...) 69 } 70 foundGrouped := found.GroupedByKey() 71 desiredGrouped := dc.Records.GroupedByKey() 72 73 // Normalize 74 models.PostProcessRecords(found) 75 76 differ := diff.New(dc) 77 changedGroups := differ.ChangedGroups(found) 78 corrections := []*models.Correction{} 79 // each name/type is given to the api as a unit. 80 for k, descs := range changedGroups { 81 key := k 82 desc := strings.Join(descs, "\n") 83 _, current := foundGrouped[k] 84 recs, wanted := desiredGrouped[k] 85 if wanted && !current { 86 // pure addition 87 corrections = append(corrections, &models.Correction{ 88 Msg: desc, 89 F: func() error { return n.add(recs, dc.Name) }, 90 }) 91 } else if current && !wanted { 92 // pure deletion 93 corrections = append(corrections, &models.Correction{ 94 Msg: desc, 95 F: func() error { return n.remove(key, dc.Name) }, 96 }) 97 } else { 98 // modification 99 corrections = append(corrections, &models.Correction{ 100 Msg: desc, 101 F: func() error { return n.modify(recs, dc.Name) }, 102 }) 103 } 104 } 105 return corrections, nil 106 } 107 108 func (n *nsone) add(recs models.Records, domain string) error { 109 _, err := n.Records.Create(buildRecord(recs, domain, "")) 110 return err 111 } 112 113 func (n *nsone) remove(key models.RecordKey, domain string) error { 114 _, err := n.Records.Delete(domain, key.NameFQDN, key.Type) 115 return err 116 } 117 118 func (n *nsone) modify(recs models.Records, domain string) error { 119 _, err := n.Records.Update(buildRecord(recs, domain, "")) 120 return err 121 } 122 123 func buildRecord(recs models.Records, domain string, id string) *dns.Record { 124 r := recs[0] 125 rec := &dns.Record{ 126 Domain: r.GetLabelFQDN(), 127 Type: r.Type, 128 ID: id, 129 TTL: int(r.TTL), 130 Zone: domain, 131 } 132 for _, r := range recs { 133 if r.Type == "TXT" { 134 rec.AddAnswer(&dns.Answer{Rdata: r.TxtStrings}) 135 } else if r.Type == "SRV" { 136 rec.AddAnswer(&dns.Answer{Rdata: strings.Split(fmt.Sprintf("%d %d %d %v", r.SrvPriority, r.SrvWeight, r.SrvPort, r.GetTargetField()), " ")}) 137 } else { 138 rec.AddAnswer(&dns.Answer{Rdata: strings.Split(r.GetTargetField(), " ")}) 139 } 140 } 141 return rec 142 } 143 144 func convert(zr *dns.ZoneRecord, domain string) ([]*models.RecordConfig, error) { 145 found := []*models.RecordConfig{} 146 for _, ans := range zr.ShortAns { 147 rec := &models.RecordConfig{ 148 TTL: uint32(zr.TTL), 149 Original: zr, 150 } 151 rec.SetLabelFromFQDN(zr.Domain, domain) 152 switch rtype := zr.Type; rtype { 153 default: 154 if err := rec.PopulateFromString(rtype, ans, domain); err != nil { 155 panic(fmt.Errorf("unparsable record received from ns1: %w", err)) 156 } 157 } 158 found = append(found, rec) 159 } 160 return found, nil 161 }