1
0
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:
Craig Peterson
2016-08-22 18:31:50 -06:00
commit ef0bbf53af
359 changed files with 157476 additions and 0 deletions

View 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))
)
```

View 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
}

View 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
}
}

View 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"}}`)

View 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()
}