mirror of
				https://github.com/StackExchange/dnscontrol.git
				synced 2024-05-11 05:55:12 +00:00 
			
		
		
		
	New provider: LuaDNS (#2127)
This commit is contained in:
		
							
								
								
									
										1
									
								
								OWNERS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								OWNERS
									
									
									
									
									
								
							@@ -25,6 +25,7 @@ providers/internetbs @pragmaton
 | 
			
		||||
providers/inwx @patschi
 | 
			
		||||
providers/msdns @tlimoncelli
 | 
			
		||||
providers/linode @koesie10
 | 
			
		||||
providers/luadns @riku22
 | 
			
		||||
providers/namecheap @willpower232
 | 
			
		||||
# providers/namedotcom NEEDS VOLUNTEER
 | 
			
		||||
providers/netcup @kordianbruck
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,7 @@ Currently supported DNS providers:
 | 
			
		||||
- Hurricane Electric DNS
 | 
			
		||||
- INWX
 | 
			
		||||
- Linode
 | 
			
		||||
- LuaDNS
 | 
			
		||||
- Microsoft Windows Server DNS Server
 | 
			
		||||
- NS1
 | 
			
		||||
- Name.com
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,7 @@
 | 
			
		||||
    * [Internet.bs](providers/internetbs.md)
 | 
			
		||||
    * [INWX](providers/inwx.md)
 | 
			
		||||
    * [Linode](providers/linode.md)
 | 
			
		||||
    * [LuaDNS](providers/luadns.md)
 | 
			
		||||
    * [Microsoft DNS Server on Microsoft Windows Server](providers/msdns.md)
 | 
			
		||||
    * [Namecheap](providers/namecheap.md)
 | 
			
		||||
    * [Name.com](providers/namedotcom.md)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ If a feature is definitively not supported for whatever reason, we would also li
 | 
			
		||||
| `INTERNETBS` | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ | ❔ |
 | 
			
		||||
| `INWX` | ❌ | ✅ | ✅ | ❌ | ❔ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ |
 | 
			
		||||
| `LINODE` | ❌ | ✅ | ❌ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ❌ | ✅ | ✅ |
 | 
			
		||||
| `LUADNS` | ✅ | ✅ | ❌ | ✅ | ❔ | ✅ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ |
 | 
			
		||||
| `MSDNS` | ✅ | ✅ | ❌ | ❌ | ❔ | ❌ | ✅ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❌ | ❌ | ✅ | ✅ |
 | 
			
		||||
| `NAMECHEAP` | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ❌ | ❔ | ❔ | ❌ | ❔ | ❌ | ❔ | ❌ | ❌ | ❌ | ✅ |
 | 
			
		||||
| `NAMEDOTCOM` | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ❌ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ✅ | ❌ | ✅ | ✅ |
 | 
			
		||||
@@ -120,6 +121,7 @@ Providers in this category and their maintainers are:
 | 
			
		||||
|`INTERNETBS`|@pragmaton|
 | 
			
		||||
|`INWX`|@svenpeter42|
 | 
			
		||||
|`LINODE`|@koesie10|
 | 
			
		||||
|`LUADNS`|@riku22|
 | 
			
		||||
|`NAMECHEAP`|@willpower232|
 | 
			
		||||
|`NETCUP`|@kordianbruck|
 | 
			
		||||
|`NETLIFY`|@SphericalKat|
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								documentation/providers/luadns.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								documentation/providers/luadns.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
To use this provider, add an entry to `creds.json` with `TYPE` set to `LUADNS`
 | 
			
		||||
along with your [email and API key](https://www.luadns.com/api.html#authentication).
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
{% code title="creds.json" %}
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "luadns": {
 | 
			
		||||
    "TYPE": "LUADNS",
 | 
			
		||||
    "email": "your-email",
 | 
			
		||||
    "apikey": "your-api-key"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
{% endcode %}
 | 
			
		||||
 | 
			
		||||
## Metadata
 | 
			
		||||
This provider does not recognize any special metadata fields unique to LuaDNS.
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
An example `dnsconfig.js` configuration:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
var REG_NONE = NewRegistrar("none");
 | 
			
		||||
var DSP_LUADNS = NewDnsProvider("luadns");
 | 
			
		||||
 | 
			
		||||
D("example.tld", REG_NONE, DnsProvider(DSP_LUADNS),
 | 
			
		||||
    A("test", "1.2.3.4")
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Activation
 | 
			
		||||
[Create API key](https://api.luadns.com/api_keys).
 | 
			
		||||
 | 
			
		||||
## Caveats
 | 
			
		||||
- LuaDNS cannot change the default nameserver TTL in `nameserver_ttl`, it is forced to fixed at 86400("1d").
 | 
			
		||||
This is not the case if you are using vanity nameservers.
 | 
			
		||||
- This provider does not currently support the "FORWARD" and "REDIRECT" record types.
 | 
			
		||||
- The API is available on the LuaDNS free plan, but due to the limit of 30 records, some tests will fail when doing integration tests.
 | 
			
		||||
@@ -134,6 +134,11 @@
 | 
			
		||||
    "domain": "$LINODE_DOMAIN",
 | 
			
		||||
    "token": "$LINODE_TOKEN"
 | 
			
		||||
  },
 | 
			
		||||
  "LUADNS": {
 | 
			
		||||
    "domain": "$LUADNS_DOMAIN",
 | 
			
		||||
    "email": "$LUADNS_EMAIL",
 | 
			
		||||
    "apikey": "$LUADNS_APIKEY"
 | 
			
		||||
  },
 | 
			
		||||
  "MSDNS": {
 | 
			
		||||
    "domain": "$MSDNS_DOMAIN",
 | 
			
		||||
    "dnsserver": "$MSDNS_DNSSERVER",
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ import (
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/internetbs"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/inwx"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/linode"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/luadns"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/msdns"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/namecheap"
 | 
			
		||||
	_ "github.com/StackExchange/dnscontrol/v3/providers/namedotcom"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										251
									
								
								providers/luadns/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								providers/luadns/api.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,251 @@
 | 
			
		||||
package luadns
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Api layer for LuaDNS
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	apiURL = "https://api.luadns.com/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type luadnsProvider struct {
 | 
			
		||||
	domainIndex      map[string]uint32
 | 
			
		||||
	nameserversNames []string
 | 
			
		||||
	creds            struct {
 | 
			
		||||
		email  string
 | 
			
		||||
		apikey string
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type errorResponse struct {
 | 
			
		||||
	Status    string `json:"status"`
 | 
			
		||||
	RequestID string `json:"request_id"`
 | 
			
		||||
	Message   string `json:"message"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type userInfoResponse struct {
 | 
			
		||||
	Email       string   `json:"email"`
 | 
			
		||||
	Name        string   `json:"name"`
 | 
			
		||||
	TTL         uint32   `json:"ttl"`
 | 
			
		||||
	NameServers []string `json:"name_servers"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type zoneRecord struct {
 | 
			
		||||
	ID   uint32 `json:"id"`
 | 
			
		||||
	Name string `json:"name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type zoneResponse []zoneRecord
 | 
			
		||||
 | 
			
		||||
type domainRecord struct {
 | 
			
		||||
	ID      uint32 `json:"id"`
 | 
			
		||||
	Type    string `json:"type"`
 | 
			
		||||
	Name    string `json:"name"`
 | 
			
		||||
	Content string `json:"content"`
 | 
			
		||||
	TTL     uint32 `json:"ttl"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type recordResponse []domainRecord
 | 
			
		||||
 | 
			
		||||
type requestParams map[string]string
 | 
			
		||||
type jsonRequestParams map[string]any
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) fetchAvailableNameservers() error {
 | 
			
		||||
	l.nameserversNames = nil
 | 
			
		||||
	var bodyString, err = l.get("/users/me", "GET", requestParams{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed fetching available nameservers list from LuaDNS: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	var ui userInfoResponse
 | 
			
		||||
	json.Unmarshal(bodyString, &ui)
 | 
			
		||||
	l.nameserversNames = ui.NameServers
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) fetchDomainList() error {
 | 
			
		||||
	l.domainIndex = map[string]uint32{}
 | 
			
		||||
	var bodyString, err = l.get("/zones", "GET", requestParams{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed fetching domain list from LuaDNS: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	var dr zoneResponse
 | 
			
		||||
	json.Unmarshal(bodyString, &dr)
 | 
			
		||||
	for _, domain := range dr {
 | 
			
		||||
		l.domainIndex[domain.Name] = domain.ID
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) getDomainID(name string) (uint32, error) {
 | 
			
		||||
	if l.domainIndex == nil {
 | 
			
		||||
		if err := l.fetchDomainList(); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := l.domainIndex[name]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return 0, fmt.Errorf("'%s' not a zone in luadns account", name)
 | 
			
		||||
	}
 | 
			
		||||
	return id, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) createDomain(domain string) error {
 | 
			
		||||
	params := jsonRequestParams{
 | 
			
		||||
		"name": domain,
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := l.get("/zones", "POST", params); err != nil {
 | 
			
		||||
		return fmt.Errorf("failed create domain (LuaDNS): %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) createRecord(domainID uint32, rec jsonRequestParams) error {
 | 
			
		||||
	if _, err := l.get(fmt.Sprintf("/zones/%d/records", domainID), "POST", rec); err != nil {
 | 
			
		||||
		return fmt.Errorf("failed create record (LuaDNS): %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) deleteRecord(domainID uint32, recordID uint32) error {
 | 
			
		||||
	if _, err := l.get(fmt.Sprintf("/zones/%d/records/%d", domainID, recordID), "DELETE", requestParams{}); err != nil {
 | 
			
		||||
		return fmt.Errorf("failed delete record (LuaDNS): %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) modifyRecord(domainID uint32, recordID uint32, rec jsonRequestParams) error {
 | 
			
		||||
	if _, err := l.get(fmt.Sprintf("/zones/%d/records/%d", domainID, recordID), "PUT", rec); err != nil {
 | 
			
		||||
		return fmt.Errorf("failed update (LuaDNS): %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) getRecords(domainID uint32) ([]domainRecord, error) {
 | 
			
		||||
	var bodyString, err = l.get(fmt.Sprintf("/zones/%d/records", domainID), "GET", requestParams{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed fetching record list from LuaDNS: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	var dr recordResponse
 | 
			
		||||
	json.Unmarshal(bodyString, &dr)
 | 
			
		||||
	var records []domainRecord
 | 
			
		||||
	for _, rec := range dr {
 | 
			
		||||
		if rec.Type == "SOA" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		records = append(records, rec)
 | 
			
		||||
	}
 | 
			
		||||
	return records, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) get(endpoint string, method string, params any) ([]byte, error) {
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	var req, err = l.makeRequest(endpoint, method, params)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []byte{}, err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Accept", "application/json")
 | 
			
		||||
	req.SetBasicAuth(l.creds.email, l.creds.apikey)
 | 
			
		||||
	// LuaDNS has a rate limit of 1200 request per 5 minute.
 | 
			
		||||
	// So we do a very primitive rate limiting here - delay every request for 250ms - so max. 4 requests/second.
 | 
			
		||||
	time.Sleep(250 * time.Millisecond)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode == 200 {
 | 
			
		||||
		bodyString, _ := io.ReadAll(resp.Body)
 | 
			
		||||
		return bodyString, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bodyString, _ := io.ReadAll(resp.Body)
 | 
			
		||||
 | 
			
		||||
	var errResp errorResponse
 | 
			
		||||
	err = json.Unmarshal(bodyString, &errResp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return bodyString, fmt.Errorf("LuaDNS API Error: %s URL:%s%s", string(bodyString), req.Host, req.URL.RequestURI())
 | 
			
		||||
	}
 | 
			
		||||
	return bodyString, fmt.Errorf("LuaDNS API error: %s URL:%s%s", errResp.Message, req.Host, req.URL.RequestURI())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) makeRequest(endpoint string, method string, params any) (*http.Request, error) {
 | 
			
		||||
	switch v := params.(type) {
 | 
			
		||||
	case requestParams:
 | 
			
		||||
		req, err := http.NewRequest(method, apiURL+endpoint, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		q := req.URL.Query()
 | 
			
		||||
		for pName, pValue := range v {
 | 
			
		||||
			q.Add(pName, pValue)
 | 
			
		||||
		}
 | 
			
		||||
		req.URL.RawQuery = q.Encode()
 | 
			
		||||
		return req, nil
 | 
			
		||||
	case jsonRequestParams:
 | 
			
		||||
		requestJSON, err := json.Marshal(params)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		req, err := http.NewRequest(method, apiURL+endpoint, bytes.NewBuffer(requestJSON))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
		return req, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("invalid request type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nativeToRecord(domain string, r *domainRecord) *models.RecordConfig {
 | 
			
		||||
	rc := &models.RecordConfig{
 | 
			
		||||
		Type:     r.Type,
 | 
			
		||||
		TTL:      r.TTL,
 | 
			
		||||
		Original: r,
 | 
			
		||||
	}
 | 
			
		||||
	rc.SetLabelFromFQDN(r.Name, domain)
 | 
			
		||||
	switch rtype := rc.Type; rtype {
 | 
			
		||||
	case "TXT":
 | 
			
		||||
		rc.SetTargetTXT(r.Content)
 | 
			
		||||
	default:
 | 
			
		||||
		rc.PopulateFromString(rtype, r.Content, domain)
 | 
			
		||||
	}
 | 
			
		||||
	return rc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func recordsToNative(rc *models.RecordConfig) jsonRequestParams {
 | 
			
		||||
	r := jsonRequestParams{
 | 
			
		||||
		"name": fmt.Sprintf("%s.", rc.GetLabelFQDN()),
 | 
			
		||||
		"type": rc.Type,
 | 
			
		||||
		"ttl":  rc.TTL,
 | 
			
		||||
	}
 | 
			
		||||
	switch rtype := rc.Type; rtype {
 | 
			
		||||
	case "TXT":
 | 
			
		||||
		r["content"] = rc.GetTargetTXTJoined()
 | 
			
		||||
	default:
 | 
			
		||||
		r["content"] = rc.GetTargetCombined()
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkNS(dc *models.DomainConfig) {
 | 
			
		||||
	newList := make([]*models.RecordConfig, 0, len(dc.Records))
 | 
			
		||||
	for _, rec := range dc.Records {
 | 
			
		||||
		// LuaDNS does not support changing the TTL of the default nameservers, so forcefully change the TTL to 86400.
 | 
			
		||||
		if rec.Type == "NS" && strings.HasSuffix(rec.GetTargetField(), ".luadns.net.") && rec.TTL != 86400 {
 | 
			
		||||
			rec.TTL = 86400
 | 
			
		||||
		}
 | 
			
		||||
		newList = append(newList, rec)
 | 
			
		||||
	}
 | 
			
		||||
	dc.Records = newList
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								providers/luadns/auditrecords.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								providers/luadns/auditrecords.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package luadns
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/models"
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/pkg/rejectif"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AuditRecords returns a list of errors corresponding to the records
 | 
			
		||||
// that aren't supported by this provider.  If all records are
 | 
			
		||||
// supported, an empty list is returned.
 | 
			
		||||
func AuditRecords(records []*models.RecordConfig) []error {
 | 
			
		||||
	a := rejectif.Auditor{}
 | 
			
		||||
	a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2023-03-03
 | 
			
		||||
	return a.Audit(records)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										212
									
								
								providers/luadns/luadnsProvider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								providers/luadns/luadnsProvider.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,212 @@
 | 
			
		||||
package luadns
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/models"
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 | 
			
		||||
	//	"github.com/StackExchange/dnscontrol/v3/pkg/transform"
 | 
			
		||||
	"github.com/StackExchange/dnscontrol/v3/providers"
 | 
			
		||||
	// "github.com/miekg/dns/dnsutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
LuaDNS API DNS provider:
 | 
			
		||||
 | 
			
		||||
Info required in `creds.json`:
 | 
			
		||||
   - email
 | 
			
		||||
   - apikey
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var features = providers.DocumentationNotes{
 | 
			
		||||
	providers.CanGetZones:            providers.Can(),
 | 
			
		||||
	providers.CanUseAlias:            providers.Can(),
 | 
			
		||||
	providers.CanUseCAA:              providers.Can(),
 | 
			
		||||
	providers.CanUsePTR:              providers.Can(),
 | 
			
		||||
	providers.CanUseSRV:              providers.Can(),
 | 
			
		||||
	providers.CanUseSSHFP:            providers.Can(),
 | 
			
		||||
	providers.CanUseTLSA:             providers.Can(),
 | 
			
		||||
	providers.DocCreateDomains:       providers.Can(),
 | 
			
		||||
	providers.DocDualHost:            providers.Can(),
 | 
			
		||||
	providers.DocOfficiallySupported: providers.Can(),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	fns := providers.DspFuncs{
 | 
			
		||||
		Initializer:   NewLuaDNS,
 | 
			
		||||
		RecordAuditor: AuditRecords,
 | 
			
		||||
	}
 | 
			
		||||
	providers.RegisterDomainServiceProviderType("LUADNS", fns, features)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLuaDNS creates the provider.
 | 
			
		||||
func NewLuaDNS(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
 | 
			
		||||
	l := &luadnsProvider{}
 | 
			
		||||
	l.creds.email, l.creds.apikey = m["email"], m["apikey"]
 | 
			
		||||
	if l.creds.email == "" || l.creds.apikey == "" {
 | 
			
		||||
		return nil, fmt.Errorf("missing LuaDNS email or apikey")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get a domain to validate authentication
 | 
			
		||||
	if err := l.fetchDomainList(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return l, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetNameservers returns the nameservers for a domain.
 | 
			
		||||
func (l *luadnsProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
 | 
			
		||||
	if len(l.nameserversNames) == 0 {
 | 
			
		||||
		l.fetchAvailableNameservers()
 | 
			
		||||
	}
 | 
			
		||||
	return models.ToNameserversStripTD(l.nameserversNames)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListZones returns a list of the DNS zones.
 | 
			
		||||
func (l *luadnsProvider) ListZones() ([]string, error) {
 | 
			
		||||
	if err := l.fetchDomainList(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	zones := make([]string, 0, len(l.domainIndex))
 | 
			
		||||
	for d := range l.domainIndex {
 | 
			
		||||
		zones = append(zones, d)
 | 
			
		||||
	}
 | 
			
		||||
	return zones, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
 | 
			
		||||
func (l *luadnsProvider) GetZoneRecords(domain string) (models.Records, error) {
 | 
			
		||||
	domainID, err := l.getDomainID(domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	records, err := l.getRecords(domainID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	existingRecords := make([]*models.RecordConfig, len(records))
 | 
			
		||||
	for i := range records {
 | 
			
		||||
		existingRecords[i] = nativeToRecord(domain, &records[i])
 | 
			
		||||
	}
 | 
			
		||||
	return existingRecords, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDomainCorrections returns a list of corrections to update a domain.
 | 
			
		||||
func (l *luadnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
 | 
			
		||||
	err := dc.Punycode()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	domainID, err := l.getDomainID(dc.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	records, err := l.GetZoneRecords(dc.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkNS(dc)
 | 
			
		||||
 | 
			
		||||
	// Normalize
 | 
			
		||||
	models.PostProcessRecords(records)
 | 
			
		||||
 | 
			
		||||
	var corrections []*models.Correction
 | 
			
		||||
	var corrs []*models.Correction
 | 
			
		||||
	if !diff2.EnableDiff2 {
 | 
			
		||||
		differ := diff.New(dc)
 | 
			
		||||
		_, create, del, mod, err := differ.IncrementalDiff(records)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		corrections := []*models.Correction{}
 | 
			
		||||
		for _, d := range del {
 | 
			
		||||
			corrs := l.makeDeleteCorrection(d.Existing, domainID, d.String())
 | 
			
		||||
			corrections = append(corrections, corrs...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, d := range create {
 | 
			
		||||
			corrs := l.makeCreateCorrection(d.Desired, domainID, d.String())
 | 
			
		||||
			corrections = append(corrections, corrs...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, d := range mod {
 | 
			
		||||
			corrs := l.makeChangeCorrection(d.Existing, d.Desired, domainID, d.String())
 | 
			
		||||
			corrections = append(corrections, corrs...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return corrections, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changes, err := diff2.ByRecord(records, dc, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, change := range changes {
 | 
			
		||||
		msg := change.Msgs[0]
 | 
			
		||||
		switch change.Type {
 | 
			
		||||
		case diff2.REPORT:
 | 
			
		||||
			corrs = []*models.Correction{{Msg: change.MsgsJoined}}
 | 
			
		||||
		case diff2.CREATE:
 | 
			
		||||
			corrs = l.makeCreateCorrection(change.New[0], domainID, msg)
 | 
			
		||||
		case diff2.CHANGE:
 | 
			
		||||
			corrs = l.makeChangeCorrection(change.Old[0], change.New[0], domainID, msg)
 | 
			
		||||
		case diff2.DELETE:
 | 
			
		||||
			corrs = l.makeDeleteCorrection(change.Old[0], domainID, msg)
 | 
			
		||||
		default:
 | 
			
		||||
			panic(fmt.Sprintf("unhandled inst.Type %s", change.Type))
 | 
			
		||||
		}
 | 
			
		||||
		corrections = append(corrections, corrs...)
 | 
			
		||||
	}
 | 
			
		||||
	return corrections, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) makeCreateCorrection(newrec *models.RecordConfig, domainID uint32, msg string) []*models.Correction {
 | 
			
		||||
	req := recordsToNative(newrec)
 | 
			
		||||
	return []*models.Correction{{
 | 
			
		||||
		Msg: msg,
 | 
			
		||||
		F: func() error {
 | 
			
		||||
			return l.createRecord(domainID, req)
 | 
			
		||||
		},
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) makeChangeCorrection(oldrec *models.RecordConfig, newrec *models.RecordConfig, domainID uint32, msg string) []*models.Correction {
 | 
			
		||||
	recordID := oldrec.Original.(*domainRecord).ID
 | 
			
		||||
	req := recordsToNative(newrec)
 | 
			
		||||
	return []*models.Correction{{
 | 
			
		||||
		Msg: fmt.Sprintf("%s, LuaDNS ID: %d", msg, recordID),
 | 
			
		||||
		F: func() error {
 | 
			
		||||
			return l.modifyRecord(domainID, recordID, req)
 | 
			
		||||
		},
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *luadnsProvider) makeDeleteCorrection(deleterec *models.RecordConfig, domainID uint32, msg string) []*models.Correction {
 | 
			
		||||
	recordID := deleterec.Original.(*domainRecord).ID
 | 
			
		||||
	return []*models.Correction{{
 | 
			
		||||
		Msg: fmt.Sprintf("%s, LuaDNS ID: %d", msg, recordID),
 | 
			
		||||
		F: func() error {
 | 
			
		||||
			return l.deleteRecord(domainID, recordID)
 | 
			
		||||
		},
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureZoneExists creates a zone if it does not exist
 | 
			
		||||
func (l *luadnsProvider) EnsureZoneExists(domain string) error {
 | 
			
		||||
	if l.domainIndex == nil {
 | 
			
		||||
		if err := l.fetchDomainList(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := l.domainIndex[domain]; ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return l.createDomain(domain)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user