mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Domainnameshop provider (#1625)
* Added basic structure for domain name shop * Finished proof of concept for domainnameshop * Fixed handeling of IDNA for CNAME records * Updated documentation notes * Added docs * Ran linter and vet * Removed proxy config used for debugging * Ran go generate * Fixed issue with TTLs being restricted to a multiple of 60 * Ran tests, vet and linting and fixed flaws * Fixed typo in docs * Improved code based on feedback * Fixed issues with TXT records not working properly * Refactored according to new file layout proposed * Updated documentation matrix * Suggestions and corrections * Corrected according to suggestions Co-authored-by: Simen Bai <git@simenbai.no> Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
223
providers/domainnameshop/api.go
Normal file
223
providers/domainnameshop/api.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package domainnameshop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
var rootAPIURI = "https://api.domeneshop.no/v0"
|
||||
|
||||
func (api *domainNameShopProvider) getDomains(domainName string) ([]domainResponse, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, rootAPIURI+"/domains?domain="+domainName, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(api.Token, api.Secret)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var domainResp []domainResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&domainResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if domainName != "" && domainName != domainResp[0].Domain {
|
||||
return nil, fmt.Errorf("invalid domain name: %q != %q", domainName, domainResp[0].Domain)
|
||||
}
|
||||
return domainResp, nil
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) getDomainID(domainName string) (string, error) {
|
||||
domainResp, err := api.getDomains(domainName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(domainResp[0].ID), nil
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) getNS(domainName string) ([]string, error) {
|
||||
domainResp, err := api.getDomains(domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domainResp[0].Nameservers, nil
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) getDNS(domainName string) ([]domainNameShopRecord, error) {
|
||||
domainID, err := api.getDomainID(domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(http.MethodGet, rootAPIURI+"/domains/"+domainID+"/dns", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(api.Token, api.Secret)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var domainResponse []domainNameShopRecord
|
||||
err = json.NewDecoder(resp.Body).Decode(&domainResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Post processing of the data received. Converting to correct types and setting default values.
|
||||
for i := range domainResponse {
|
||||
// Convert priority from string to Uint, defaulting to 0
|
||||
record := &domainResponse[i]
|
||||
priority, err := strconv.ParseUint(record.Priority, 10, 16)
|
||||
if err != nil {
|
||||
record.ActualPriority = 0
|
||||
}
|
||||
record.ActualPriority = uint16(priority)
|
||||
|
||||
// Convert port from string to Uint, defaulting to 0
|
||||
port, err := strconv.ParseUint(record.Port, 10, 16)
|
||||
if err != nil {
|
||||
record.ActualPort = 0
|
||||
}
|
||||
record.ActualPort = uint16(port)
|
||||
|
||||
// Convert weight from string ti Uint, defaulting to 0
|
||||
weight, err := strconv.ParseUint(record.Weight, 10, 16)
|
||||
if err != nil {
|
||||
record.ActualWeight = 0
|
||||
}
|
||||
record.ActualWeight = uint16(weight)
|
||||
|
||||
// Converting the CAA flag from string to correct value
|
||||
if record.Type == "CAA" {
|
||||
CaaFlag, err := strconv.ParseUint(record.ActualCAAFlag, 10, 8)
|
||||
if err != nil {
|
||||
record.CAAFlag = 0
|
||||
}
|
||||
record.CAAFlag = CaaFlag
|
||||
}
|
||||
|
||||
// Transform data field to punycode if CNAME
|
||||
if record.Type == "CNAME" {
|
||||
punycodeData, err := idna.ToASCII(record.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record.Data = punycodeData
|
||||
if !strings.HasSuffix(record.Data, ".") {
|
||||
record.Data += "."
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the TTL.
|
||||
record.TTL = uint16(fixTTL(uint32(record.TTL)))
|
||||
|
||||
// Add domain id
|
||||
(&domainResponse[i]).DomainID = domainID
|
||||
}
|
||||
|
||||
ns, err := api.getNS(domainName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Adds NS as records
|
||||
for _, nameserver := range ns {
|
||||
domainResponse = append(domainResponse, domainNameShopRecord{
|
||||
ID: 0,
|
||||
Host: "@",
|
||||
TTL: 300,
|
||||
Type: "NS",
|
||||
Data: nameserver + ".",
|
||||
DomainID: domainID,
|
||||
})
|
||||
}
|
||||
|
||||
return domainResponse, nil
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) deleteRecord(domainID string, recordID string) error {
|
||||
return api.sendChangeRequest(http.MethodDelete, rootAPIURI+"/domains/"+domainID+"/dns/"+recordID, nil)
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) CreateRecord(domainName string, dnsR *domainNameShopRecord) error {
|
||||
domainID, err := api.getDomainID(domainName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloadBuf := new(bytes.Buffer)
|
||||
err = json.NewEncoder(payloadBuf).Encode(&dnsR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.sendChangeRequest(http.MethodPost, rootAPIURI+"/domains/"+domainID+"/dns", payloadBuf)
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) UpdateRecord(dnsR *domainNameShopRecord) error {
|
||||
domainID := dnsR.DomainID
|
||||
recordID := strconv.Itoa(dnsR.ID)
|
||||
|
||||
payloadBuf := new(bytes.Buffer)
|
||||
json.NewEncoder(payloadBuf).Encode(&dnsR)
|
||||
|
||||
return api.sendChangeRequest(http.MethodPut, rootAPIURI+"/domains/"+domainID+"/dns/"+recordID, payloadBuf)
|
||||
}
|
||||
|
||||
func (api *domainNameShopProvider) sendChangeRequest(method string, uri string, payload *bytes.Buffer) error {
|
||||
client := &http.Client{}
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
if payload != nil {
|
||||
req, err = http.NewRequest(method, uri, payload)
|
||||
} else {
|
||||
req, err = http.NewRequest(method, uri, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(api.Token, api.Secret)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case 201:
|
||||
// Record is deleted
|
||||
return nil
|
||||
case 204:
|
||||
//Update successful
|
||||
return nil
|
||||
case 400:
|
||||
return fmt.Errorf("DNS record failed validation")
|
||||
case 403:
|
||||
return fmt.Errorf("not authorized")
|
||||
case 404:
|
||||
return fmt.Errorf("does not exist")
|
||||
case 409:
|
||||
return fmt.Errorf("collision")
|
||||
default:
|
||||
return fmt.Errorf("unknown statuscode: %v", resp.StatusCode)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user