mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
migrate code for github
This commit is contained in:
48
providers/namedotcom/namedotcom.md
Normal file
48
providers/namedotcom/namedotcom.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## name.com Provider
|
||||
|
||||
### required config
|
||||
|
||||
In your providers config json file you must provide your name.com api username and access token:
|
||||
|
||||
```
|
||||
"yourNameDotComProviderName":{
|
||||
"apikey": "yourApiKeyFromName.com-klasjdkljasdlk235235235235",
|
||||
"apiuser": "yourUsername"
|
||||
}
|
||||
```
|
||||
|
||||
In order to get api access you need to [apply for access](https://www.name.com/reseller/apply)
|
||||
|
||||
### example dns config js (registrar only):
|
||||
|
||||
```
|
||||
var NAMECOM = NewRegistrar("myNameCom","NAMEDOTCOM");
|
||||
|
||||
var mynameServers = [
|
||||
NAMESERVER("bill.ns.cloudflare.com"),
|
||||
NAMESERVER("fred.ns.cloudflare.com")
|
||||
];
|
||||
|
||||
D("example.tld",NAMECOM,myNameServers
|
||||
//records handled by another provider...
|
||||
);
|
||||
```
|
||||
|
||||
### example config (registrar and records managed by namedotcom)
|
||||
|
||||
```
|
||||
var NAMECOM = NewRegistrar("myNameCom","NAMEDOTCOM");
|
||||
var NAMECOMDSP = NewDSP("myNameCom","NAMEDOTCOM")
|
||||
|
||||
D("exammple.tld", NAMECOM, NAMECOMDSP,
|
||||
//ns[1-4].name.com used by default as nameservers
|
||||
|
||||
//override default ttl of 300s
|
||||
DefaultTTL(3600),
|
||||
|
||||
A("test","1.2.3.4"),
|
||||
|
||||
//override ttl for one record only
|
||||
CNAME("foo","some.otherdomain.tld.",TTL(100))
|
||||
)
|
||||
```
|
117
providers/namedotcom/namedotcomProvider.go
Normal file
117
providers/namedotcom/namedotcomProvider.go
Normal file
@@ -0,0 +1,117 @@
|
||||
//Package namedotcom implements a registrar that uses the name.com api to set name servers. It will self register it's providers when imported.
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/providers"
|
||||
)
|
||||
|
||||
type nameDotCom struct {
|
||||
APIUser string `json:"apiuser"`
|
||||
APIKey string `json:"apikey"`
|
||||
}
|
||||
|
||||
func newReg(conf map[string]string) (providers.Registrar, error) {
|
||||
return newProvider(conf)
|
||||
}
|
||||
|
||||
func newDsp(conf map[string]string, meta json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
return newProvider(conf)
|
||||
}
|
||||
|
||||
func newProvider(conf map[string]string) (*nameDotCom, error) {
|
||||
api := &nameDotCom{}
|
||||
api.APIUser, api.APIKey = conf["apiuser"], conf["apikey"]
|
||||
if api.APIKey == "" || api.APIUser == "" {
|
||||
return nil, fmt.Errorf("Name.com apikey and apiuser must be provided.")
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
providers.RegisterRegistrarType("NAMEDOTCOM", newReg)
|
||||
providers.RegisterDomainServiceProviderType("NAMEDOTCOM", newDsp)
|
||||
}
|
||||
|
||||
///
|
||||
//various http helpers for interacting with api
|
||||
///
|
||||
|
||||
func (n *nameDotCom) addAuth(r *http.Request) {
|
||||
r.Header.Add("Api-Username", n.APIUser)
|
||||
r.Header.Add("Api-Token", n.APIKey)
|
||||
}
|
||||
|
||||
type apiResult struct {
|
||||
Result struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func (r *apiResult) getErr() error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if r.Result.Code != 100 {
|
||||
if r.Result.Message == "" {
|
||||
return fmt.Errorf("Unknown error from name.com")
|
||||
}
|
||||
return fmt.Errorf(r.Result.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiBase = "https://api.name.com/api"
|
||||
|
||||
//perform http GET and unmarshal response json into target struct
|
||||
func (n *nameDotCom) get(url string, target interface{}) error {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.addAuth(req)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, target)
|
||||
}
|
||||
|
||||
// perform http POST, json marshalling the given data into the body
|
||||
func (n *nameDotCom) post(url string, data interface{}) (*apiResult, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.addAuth(req)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
text, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &apiResult{}
|
||||
if err = json.Unmarshal(text, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
82
providers/namedotcom/nameservers.go
Normal file
82
providers/namedotcom/nameservers.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
func (n *nameDotCom) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
foundNameservers, err := n.getNameservers(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if defaultNsRegexp.MatchString(foundNameservers) {
|
||||
foundNameservers = "ns1.name.com,ns2.name.com,ns3.name.com,ns4.name.com"
|
||||
}
|
||||
expected := []string{}
|
||||
for _, ns := range dc.Nameservers {
|
||||
name := strings.TrimRight(ns.Name, ".")
|
||||
expected = append(expected, name)
|
||||
}
|
||||
sort.Strings(expected)
|
||||
expectedNameservers := strings.Join(expected, ",")
|
||||
|
||||
if foundNameservers != expectedNameservers {
|
||||
return []*models.Correction{
|
||||
{
|
||||
Msg: fmt.Sprintf("Update nameservers %s -> %s", foundNameservers, expectedNameservers),
|
||||
F: n.updateNameservers(expected, dc.Name),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//even if you provide them "ns1.name.com", they will set it to "ns1qrt.name.com". This will match that pattern to see if defaults are in use.
|
||||
var defaultNsRegexp = regexp.MustCompile(`ns1[a-z]{0,3}\.name\.com,ns2[a-z]{0,3}\.name\.com,ns3[a-z]{0,3}\.name\.com,ns4[a-z]{0,3}\.name\.com`)
|
||||
|
||||
func apiGetDomain(domain string) string {
|
||||
return fmt.Sprintf("%s/domain/get/%s", apiBase, domain)
|
||||
}
|
||||
func apiUpdateNS(domain string) string {
|
||||
return fmt.Sprintf("%s/domain/update_nameservers/%s", apiBase, domain)
|
||||
}
|
||||
|
||||
type getDomainResult struct {
|
||||
*apiResult
|
||||
DomainName string `json:"domain_name"`
|
||||
Nameservers []string `json:"nameservers"`
|
||||
}
|
||||
|
||||
// returns comma joined list of nameservers (in alphabetical order)
|
||||
func (n *nameDotCom) getNameservers(domain string) (string, error) {
|
||||
result := &getDomainResult{}
|
||||
if err := n.get(apiGetDomain(domain), result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := result.getErr(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Strings(result.Nameservers)
|
||||
return strings.Join(result.Nameservers, ","), nil
|
||||
}
|
||||
|
||||
func (n *nameDotCom) updateNameservers(ns []string, domain string) func() error {
|
||||
return func() error {
|
||||
dat := struct {
|
||||
Nameservers []string `json:"nameservers"`
|
||||
}{ns}
|
||||
resp, err := n.post(apiUpdateNS(domain), dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = resp.getErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
150
providers/namedotcom/nameservers_test.go
Normal file
150
providers/namedotcom/nameservers_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
)
|
||||
|
||||
var (
|
||||
mux *http.ServeMux
|
||||
client *nameDotCom
|
||||
server *httptest.Server
|
||||
)
|
||||
|
||||
func setup() {
|
||||
mux = http.NewServeMux()
|
||||
server = httptest.NewServer(mux)
|
||||
|
||||
client = &nameDotCom{
|
||||
APIUser: "bob",
|
||||
APIKey: "123",
|
||||
}
|
||||
apiBase = server.URL
|
||||
}
|
||||
|
||||
func teardown() {
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func TestGetNameservers(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
givenNs, expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{`"foo.ns.tld","bar.ns.tld"`, "bar.ns.tld,foo.ns.tld"},
|
||||
{"ERR", "ERR"},
|
||||
{"MSGERR", "ERR"},
|
||||
} {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/domain/get/example.tld", func(w http.ResponseWriter, r *http.Request) {
|
||||
if test.givenNs == "ERR" {
|
||||
http.Error(w, "UH OH", 500)
|
||||
return
|
||||
}
|
||||
if test.givenNs == "MSGERR" {
|
||||
w.Write(nameComError)
|
||||
return
|
||||
}
|
||||
w.Write(domainResponse(test.givenNs))
|
||||
})
|
||||
|
||||
found, err := client.getNameservers("example.tld")
|
||||
if err != nil {
|
||||
if test.expected == "ERR" {
|
||||
continue
|
||||
}
|
||||
t.Errorf("Error on test %d: %s", i, err)
|
||||
continue
|
||||
}
|
||||
if test.expected == "ERR" {
|
||||
t.Errorf("Expected error on test %d, but was none", i)
|
||||
continue
|
||||
}
|
||||
if found != test.expected {
|
||||
t.Errorf("Test %d: Expected '%s', but found '%s'", i, test.expected, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCorrections(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
givenNs string
|
||||
expected int
|
||||
}{
|
||||
{"", 1},
|
||||
{`"foo.ns.tld","bar.ns.tld"`, 0},
|
||||
{`"bar.ns.tld","foo.ns.tld"`, 0},
|
||||
{`"foo.ns.tld"`, 1},
|
||||
{`"1.ns.aaa","2.ns.www"`, 1},
|
||||
{"ERR", -1}, //-1 means we expect an error
|
||||
{"MSGERR", -1},
|
||||
} {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
mux.HandleFunc("/domain/get/example.tld", func(w http.ResponseWriter, r *http.Request) {
|
||||
if test.givenNs == "ERR" {
|
||||
http.Error(w, "UH OH", 500)
|
||||
return
|
||||
}
|
||||
if test.givenNs == "MSGERR" {
|
||||
w.Write(nameComError)
|
||||
return
|
||||
}
|
||||
w.Write(domainResponse(test.givenNs))
|
||||
})
|
||||
dc := &models.DomainConfig{
|
||||
Name: "example.tld",
|
||||
Nameservers: []*models.Nameserver{
|
||||
{Name: "foo.ns.tld"},
|
||||
{Name: "bar.ns.tld"},
|
||||
},
|
||||
}
|
||||
corrections, err := client.GetRegistrarCorrections(dc)
|
||||
if err != nil {
|
||||
if test.expected == -1 {
|
||||
continue
|
||||
}
|
||||
t.Errorf("Error on test %d: %s", i, err)
|
||||
continue
|
||||
}
|
||||
if test.expected == -1 {
|
||||
t.Errorf("Expected error on test %d, but was none", i)
|
||||
continue
|
||||
}
|
||||
if len(corrections) != test.expected {
|
||||
t.Errorf("Test %d: Expected '%d', but found '%d'", i, test.expected, len(corrections))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func domainResponse(ns string) []byte {
|
||||
return []byte(fmt.Sprintf(`{
|
||||
"result": {
|
||||
"code": 100,
|
||||
"message": "Command Successful"
|
||||
},
|
||||
"domain_name": "example.tld",
|
||||
"create_date": "2015-12-28 18:08:05",
|
||||
"expire_date": "2016-12-28 23:59:59",
|
||||
"locked": true,
|
||||
"nameservers": [%s],
|
||||
"contacts": [],
|
||||
"addons": {
|
||||
"whois_privacy": {
|
||||
"price": "3.99"
|
||||
},
|
||||
"domain\/renew": {
|
||||
"price": "10.99"
|
||||
}
|
||||
}
|
||||
}`, ns))
|
||||
}
|
||||
|
||||
var nameComError = []byte(`{"result":{"code":251,"message":"Authentication Error - Invalid Username Or Api Token"}}`)
|
168
providers/namedotcom/records.go
Normal file
168
providers/namedotcom/records.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/models"
|
||||
"github.com/StackExchange/dnscontrol/providers/diff"
|
||||
)
|
||||
|
||||
var defaultNameservers = []*models.Nameserver{
|
||||
{Name: "ns1.name.com"},
|
||||
{Name: "ns2.name.com"},
|
||||
{Name: "ns3.name.com"},
|
||||
{Name: "ns4.name.com"},
|
||||
}
|
||||
|
||||
func (n *nameDotCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
dc.Nameservers = defaultNameservers
|
||||
records, err := n.getRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actual := make([]diff.Record, len(records))
|
||||
for i := range records {
|
||||
actual[i] = records[i]
|
||||
}
|
||||
|
||||
desired := make([]diff.Record, len(dc.Records))
|
||||
for i, rec := range dc.Records {
|
||||
if rec.TTL == 0 {
|
||||
rec.TTL = 300
|
||||
}
|
||||
desired[i] = rec
|
||||
}
|
||||
|
||||
_, create, del, mod := diff.IncrementalDiff(actual, desired)
|
||||
corrections := []*models.Correction{}
|
||||
|
||||
for _, d := range del {
|
||||
rec := d.Existing.(*nameComRecord)
|
||||
c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.RecordID, dc.Name) }}
|
||||
corrections = append(corrections, c)
|
||||
}
|
||||
for _, cre := range create {
|
||||
rec := cre.Desired.(*models.RecordConfig)
|
||||
c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
|
||||
corrections = append(corrections, c)
|
||||
}
|
||||
for _, chng := range mod {
|
||||
old := chng.Existing.(*nameComRecord)
|
||||
new := chng.Desired.(*models.RecordConfig)
|
||||
c := &models.Correction{Msg: chng.String(), F: func() error {
|
||||
err := n.deleteRecord(old.RecordID, dc.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.createRecord(new, dc.Name)
|
||||
}}
|
||||
corrections = append(corrections, c)
|
||||
}
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func apiGetRecords(domain string) string {
|
||||
return fmt.Sprintf("%s/dns/list/%s", apiBase, domain)
|
||||
}
|
||||
func apiCreateRecord(domain string) string {
|
||||
return fmt.Sprintf("%s/dns/create/%s", apiBase, domain)
|
||||
}
|
||||
func apiDeleteRecord(domain string) string {
|
||||
return fmt.Sprintf("%s/dns/delete/%s", apiBase, domain)
|
||||
}
|
||||
|
||||
type nameComRecord struct {
|
||||
RecordID string `json:"record_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
TTL string `json:"ttl"`
|
||||
Priority string `json:"priority"`
|
||||
}
|
||||
|
||||
func (r *nameComRecord) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
func (r *nameComRecord) GetType() string {
|
||||
return r.Type
|
||||
}
|
||||
func (r *nameComRecord) GetContent() string {
|
||||
return r.Content
|
||||
}
|
||||
func (r *nameComRecord) GetComparisionData() string {
|
||||
mxPrio := ""
|
||||
if r.Type == "MX" {
|
||||
mxPrio = fmt.Sprintf(" %s ", r.Priority)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", r.TTL, mxPrio)
|
||||
}
|
||||
|
||||
type listRecordsResponse struct {
|
||||
*apiResult
|
||||
Records []*nameComRecord `json:"records"`
|
||||
}
|
||||
|
||||
func (n *nameDotCom) getRecords(domain string) ([]*nameComRecord, error) {
|
||||
result := &listRecordsResponse{}
|
||||
err := n.get(apiGetRecords(domain), result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = result.getErr(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rc := range result.Records {
|
||||
if rc.Type == "CNAME" || rc.Type == "MX" || rc.Type == "NS" {
|
||||
rc.Content = rc.Content + "."
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result.Records, nil
|
||||
}
|
||||
|
||||
func (n *nameDotCom) createRecord(rc *models.RecordConfig, domain string) error {
|
||||
target := rc.Target
|
||||
if rc.Type == "CNAME" || rc.Type == "MX" || rc.Type == "NS" {
|
||||
if target[len(target)-1] == '.' {
|
||||
target = target[:len(target)-1]
|
||||
} else {
|
||||
return fmt.Errorf("Unexpected. CNAME/MX/NS target did not end with dot.\n")
|
||||
}
|
||||
}
|
||||
dat := struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
TTL uint32 `json:"ttl,omitempty"`
|
||||
Priority uint16 `json:"priority,omitempty"`
|
||||
}{
|
||||
Hostname: dnsutil.TrimDomainName(rc.NameFQDN, domain),
|
||||
Type: rc.Type,
|
||||
Content: target,
|
||||
TTL: rc.TTL,
|
||||
Priority: rc.Priority,
|
||||
}
|
||||
if dat.Hostname == "@" {
|
||||
dat.Hostname = ""
|
||||
}
|
||||
resp, err := n.post(apiCreateRecord(domain), dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resp.getErr()
|
||||
}
|
||||
|
||||
func (n *nameDotCom) deleteRecord(id, domain string) error {
|
||||
dat := struct {
|
||||
ID string `json:"record_id"`
|
||||
}{id}
|
||||
resp, err := n.post(apiDeleteRecord(domain), dat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resp.getErr()
|
||||
}
|
Reference in New Issue
Block a user