2023-03-09 23:15:59 +08:00
|
|
|
package luadns
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
2023-05-21 01:21:45 +08:00
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
2023-03-09 23:15:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
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(),
|
2023-03-17 02:04:20 +08:00
|
|
|
providers.CanUseLOC: providers.Cannot(),
|
2023-03-09 23:15:59 +08:00
|
|
|
providers.CanUsePTR: providers.Can(),
|
|
|
|
providers.CanUseSRV: providers.Can(),
|
|
|
|
providers.CanUseSSHFP: providers.Can(),
|
|
|
|
providers.CanUseTLSA: providers.Can(),
|
|
|
|
providers.DocCreateDomains: providers.Can(),
|
|
|
|
providers.DocDualHost: providers.Can(),
|
2023-03-16 00:12:51 +08:00
|
|
|
providers.DocOfficiallySupported: providers.Cannot(),
|
2023-03-09 23:15:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2023-05-03 01:04:59 +08:00
|
|
|
func (l *luadnsProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
|
2023-03-09 23:15:59 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-04-15 03:22:23 +08:00
|
|
|
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
|
|
|
func (l *luadnsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, records models.Records) ([]*models.Correction, error) {
|
|
|
|
|
|
|
|
checkNS(dc)
|
|
|
|
|
2023-03-09 23:15:59 +08:00
|
|
|
domainID, err := l.getDomainID(dc.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|