2021-07-07 23:43:10 +08:00
|
|
|
package transip
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-01-24 07:16:09 +08:00
|
|
|
"sort"
|
2021-07-07 23:43:10 +08:00
|
|
|
"strings"
|
|
|
|
|
2023-05-21 01:21:45 +08:00
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
2021-07-07 23:43:10 +08:00
|
|
|
"github.com/transip/gotransip/v6"
|
|
|
|
"github.com/transip/gotransip/v6/domain"
|
|
|
|
"github.com/transip/gotransip/v6/repository"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
TransIP DNS Provider (transip.nl)
|
|
|
|
|
|
|
|
Info required in `creds.json`
|
|
|
|
- AccessToken
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
type transipProvider struct {
|
|
|
|
client *repository.Client
|
|
|
|
domains *domain.Repository
|
|
|
|
}
|
|
|
|
|
|
|
|
var features = providers.DocumentationNotes{
|
2024-03-19 06:30:09 +08:00
|
|
|
// The default for unlisted capabilities is 'Cannot'.
|
|
|
|
// See providers/capabilities.go for the entire list of capabilities.
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanAutoDNSSEC: providers.Cannot(),
|
|
|
|
providers.CanGetZones: providers.Can(),
|
2024-05-13 21:03:06 +08:00
|
|
|
providers.CanConcur: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseAKAMAICDN: providers.Cannot(),
|
2022-08-08 20:35:09 +08:00
|
|
|
providers.CanUseAlias: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseAzureAlias: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanUseCAA: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseDHCID: providers.Cannot(),
|
|
|
|
providers.CanUseDNAME: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanUseDS: providers.Cannot(),
|
|
|
|
providers.CanUseDSForChildren: providers.Cannot(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseHTTPS: providers.Cannot(),
|
2023-03-17 02:04:20 +08:00
|
|
|
providers.CanUseLOC: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanUseNAPTR: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUsePTR: providers.Cannot(),
|
|
|
|
providers.CanUseRoute53Alias: providers.Cannot(),
|
|
|
|
providers.CanUseSOA: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanUseSRV: providers.Can(),
|
|
|
|
providers.CanUseSSHFP: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseSVCB: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.CanUseTLSA: providers.Can(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.CanUseDNSKEY: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.DocCreateDomains: providers.Cannot(),
|
2024-05-16 20:41:39 +08:00
|
|
|
providers.DocDualHost: providers.Cannot(),
|
2021-07-07 23:43:10 +08:00
|
|
|
providers.DocOfficiallySupported: providers.Cannot(),
|
|
|
|
}
|
|
|
|
|
2022-01-28 04:58:56 +08:00
|
|
|
// NewTransip creates a new TransIP provider.
|
2021-07-07 23:43:10 +08:00
|
|
|
func NewTransip(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
2021-07-22 00:06:29 +08:00
|
|
|
|
|
|
|
if m["AccessToken"] == "" && m["PrivateKey"] == "" {
|
|
|
|
return nil, fmt.Errorf("no TransIP AccessToken or PrivateKey provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
if m["PrivateKey"] != "" && m["AccountName"] == "" {
|
|
|
|
return nil, fmt.Errorf("no AccountName given, required for authenticating with PrivateKey")
|
2021-07-07 23:43:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
client, err := gotransip.NewClient(gotransip.ClientConfiguration{
|
2021-07-22 00:06:29 +08:00
|
|
|
Token: m["AccessToken"],
|
|
|
|
AccountName: m["AccountName"],
|
|
|
|
PrivateKeyReader: strings.NewReader(m["PrivateKey"]),
|
2021-07-07 23:43:10 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("TransIP client fail %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
api := &transipProvider{}
|
|
|
|
api.client = &client
|
|
|
|
api.domains = &domain.Repository{Client: client}
|
|
|
|
|
|
|
|
return api, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
fns := providers.DspFuncs{
|
|
|
|
Initializer: NewTransip,
|
|
|
|
RecordAuditor: AuditRecords,
|
|
|
|
}
|
|
|
|
providers.RegisterDomainServiceProviderType("TRANSIP", fns, features)
|
|
|
|
}
|
|
|
|
|
2023-01-24 07:16:09 +08:00
|
|
|
func (n *transipProvider) ListZones() ([]string, error) {
|
|
|
|
var domains []string
|
|
|
|
|
2023-06-15 21:59:15 +08:00
|
|
|
domainsMap, err := n.domains.GetAll()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-01-24 07:16:09 +08:00
|
|
|
for _, domainname := range domainsMap {
|
|
|
|
domains = append(domains, domainname.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(domains)
|
|
|
|
|
|
|
|
return domains, nil
|
|
|
|
}
|
|
|
|
|
2023-04-15 03:22:23 +08:00
|
|
|
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
|
|
|
|
func (n *transipProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, curRecords models.Records) ([]*models.Correction, error) {
|
2021-07-07 23:43:10 +08:00
|
|
|
|
|
|
|
removeOtherNS(dc)
|
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
corrections, err := n.getCorrectionsUsingDiff2(dc, curRecords)
|
|
|
|
return corrections, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *transipProvider) getCorrectionsUsingDiff2(dc *models.DomainConfig, records models.Records) ([]*models.Correction, error) {
|
2022-12-12 04:02:58 +08:00
|
|
|
var corrections []*models.Correction
|
2023-10-23 01:56:13 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
instructions, err := diff2.ByRecordSet(records, dc, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-07 23:43:10 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
for _, change := range instructions {
|
2023-05-06 23:15:13 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
switch change.Type {
|
|
|
|
case diff2.DELETE:
|
2023-05-06 23:15:13 +08:00
|
|
|
oldEntries, err := recordsToNative(change.Old, true)
|
|
|
|
if err != nil {
|
|
|
|
return corrections, err
|
|
|
|
}
|
|
|
|
correction := change.CreateCorrection(
|
|
|
|
wrapChangeFunction(
|
|
|
|
oldEntries,
|
|
|
|
func(rec domain.DNSEntry) error { return n.domains.RemoveDNSEntry(dc.Name, rec) },
|
|
|
|
),
|
|
|
|
)
|
|
|
|
corrections = append(corrections, correction)
|
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
case diff2.CREATE:
|
2023-05-06 23:15:13 +08:00
|
|
|
newEntries, err := recordsToNative(change.New, false)
|
|
|
|
if err != nil {
|
|
|
|
return corrections, err
|
|
|
|
}
|
|
|
|
correction := change.CreateCorrection(
|
|
|
|
wrapChangeFunction(
|
|
|
|
newEntries,
|
|
|
|
func(rec domain.DNSEntry) error { return n.domains.AddDNSEntry(dc.Name, rec) },
|
|
|
|
),
|
|
|
|
)
|
|
|
|
corrections = append(corrections, correction)
|
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
case diff2.CHANGE:
|
|
|
|
if canDirectApplyDNSEntries(change) {
|
2023-05-06 23:15:13 +08:00
|
|
|
newEntries, err := recordsToNative(change.New, false)
|
|
|
|
if err != nil {
|
|
|
|
return corrections, err
|
|
|
|
}
|
|
|
|
correction := change.CreateCorrection(
|
|
|
|
wrapChangeFunction(
|
|
|
|
newEntries,
|
|
|
|
func(rec domain.DNSEntry) error { return n.domains.UpdateDNSEntry(dc.Name, rec) },
|
|
|
|
),
|
|
|
|
)
|
|
|
|
corrections = append(corrections, correction)
|
2023-03-27 00:51:45 +08:00
|
|
|
} else {
|
2023-05-06 23:15:13 +08:00
|
|
|
corrections = append(
|
|
|
|
corrections,
|
2023-12-05 00:54:03 +08:00
|
|
|
n.recreateRecordSet(dc, change)...,
|
2023-05-06 23:15:13 +08:00
|
|
|
)
|
2023-03-27 00:51:45 +08:00
|
|
|
}
|
|
|
|
case diff2.REPORT:
|
|
|
|
corrections = append(corrections, change.CreateMessage())
|
2021-07-07 23:43:10 +08:00
|
|
|
}
|
2023-05-06 23:15:13 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return corrections, nil
|
|
|
|
}
|
|
|
|
|
2023-12-05 00:54:03 +08:00
|
|
|
func (n *transipProvider) recreateRecordSet(dc *models.DomainConfig, change diff2.Change) []*models.Correction {
|
|
|
|
var corrections []*models.Correction
|
|
|
|
|
|
|
|
for _, rec := range change.Old {
|
|
|
|
if existsInRecords(rec, change.New) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
nativeRec, _ := recordToNative(rec, true)
|
2024-01-12 03:35:16 +08:00
|
|
|
createCorrection := change.CreateCorrectionWithMessage("[1/2] delete", func() error { return n.domains.RemoveDNSEntry(dc.Name, nativeRec) })
|
2023-12-05 00:54:03 +08:00
|
|
|
corrections = append(corrections, createCorrection)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rec := range change.New {
|
|
|
|
if existsInRecords(rec, change.Old) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
nativeRec, _ := recordToNative(rec, false)
|
2024-01-12 03:35:16 +08:00
|
|
|
createCorrection := change.CreateCorrectionWithMessage("[2/2] create", func() error { return n.domains.AddDNSEntry(dc.Name, nativeRec) })
|
2023-12-05 00:54:03 +08:00
|
|
|
corrections = append(corrections, createCorrection)
|
|
|
|
}
|
|
|
|
|
|
|
|
return corrections
|
|
|
|
}
|
|
|
|
|
|
|
|
func existsInRecords(rec *models.RecordConfig, set models.Records) bool {
|
|
|
|
for _, existing := range set {
|
|
|
|
if rec.ToComparableNoTTL() == existing.ToComparableNoTTL() && rec.TTL == existing.TTL {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-05-06 23:15:13 +08:00
|
|
|
func recordsToNative(records models.Records, useOriginal bool) ([]domain.DNSEntry, error) {
|
|
|
|
entries := make([]domain.DNSEntry, len(records))
|
2021-07-07 23:43:10 +08:00
|
|
|
|
2023-05-06 23:15:13 +08:00
|
|
|
for iX, record := range records {
|
|
|
|
entry, err := recordToNative(record, useOriginal)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
entries[iX] = entry
|
|
|
|
}
|
2021-07-07 23:43:10 +08:00
|
|
|
|
2023-05-06 23:15:13 +08:00
|
|
|
return entries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func wrapChangeFunction(entries []domain.DNSEntry, executer func(rec domain.DNSEntry) error) func() error {
|
|
|
|
return func() error {
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
|
|
|
if err := executer(entry); err != nil {
|
2023-03-27 00:51:45 +08:00
|
|
|
return err
|
|
|
|
}
|
2021-07-07 23:43:10 +08:00
|
|
|
}
|
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2021-07-07 23:43:10 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
// canDirectApplyDNSEntries determines if a change can be done in a single API call or
|
|
|
|
// if we must remove the old records and re-create them. TransIP is unable to do certain
|
|
|
|
// changes in a single call. As we learn those situations, add them here.
|
|
|
|
func canDirectApplyDNSEntries(change diff2.Change) bool {
|
|
|
|
desired, existing := change.New, change.Old
|
|
|
|
|
|
|
|
if change.Type != diff2.CHANGE {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(desired) != len(existing) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(desired) > 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(desired); i++ {
|
|
|
|
if !canUpdateDNSEntry(desired[i], existing[i]) {
|
|
|
|
return false
|
2022-12-12 04:02:58 +08:00
|
|
|
}
|
2023-03-27 00:51:45 +08:00
|
|
|
}
|
2022-12-12 04:02:58 +08:00
|
|
|
|
2023-03-27 00:51:45 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-07-07 23:43:10 +08:00
|
|
|
func canUpdateDNSEntry(desired *models.RecordConfig, existing *models.RecordConfig) bool {
|
|
|
|
return desired.Name == existing.Name && desired.TTL == existing.TTL && desired.Type == existing.Type
|
|
|
|
}
|
|
|
|
|
2023-05-03 01:04:59 +08:00
|
|
|
func (n *transipProvider) GetZoneRecords(domainName string, meta map[string]string) (models.Records, error) {
|
2021-07-07 23:43:10 +08:00
|
|
|
|
|
|
|
entries, err := n.domains.GetDNSEntries(domainName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var existingRecords = []*models.RecordConfig{}
|
|
|
|
for _, entry := range entries {
|
|
|
|
rts, err := nativeToRecord(entry, domainName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
existingRecords = append(existingRecords, rts)
|
|
|
|
}
|
|
|
|
|
|
|
|
return existingRecords, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *transipProvider) GetNameservers(domainName string) ([]*models.Nameserver, error) {
|
|
|
|
var nss []string
|
|
|
|
|
|
|
|
entries, err := n.domains.GetNameservers(domainName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
|
|
nss = append(nss, entry.Hostname)
|
|
|
|
}
|
|
|
|
|
|
|
|
return models.ToNameservers(nss)
|
|
|
|
}
|
|
|
|
|
2023-12-05 06:45:25 +08:00
|
|
|
// recordToNative convrts RecordConfig TO Native.
|
2023-05-06 23:15:13 +08:00
|
|
|
func recordToNative(config *models.RecordConfig, useOriginal bool) (domain.DNSEntry, error) {
|
|
|
|
if useOriginal && config.Original != nil {
|
|
|
|
return config.Original.(domain.DNSEntry), nil
|
|
|
|
}
|
|
|
|
|
2021-07-07 23:43:10 +08:00
|
|
|
return domain.DNSEntry{
|
|
|
|
Name: config.Name,
|
|
|
|
Expire: int(config.TTL),
|
|
|
|
Type: config.Type,
|
2023-12-11 09:47:44 +08:00
|
|
|
Content: config.GetTargetCombinedFunc(nil),
|
2021-07-07 23:43:10 +08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-12-05 06:45:25 +08:00
|
|
|
// nativeToRecord converts native to RecordConfig.
|
2021-07-07 23:43:10 +08:00
|
|
|
func nativeToRecord(entry domain.DNSEntry, origin string) (*models.RecordConfig, error) {
|
|
|
|
rc := &models.RecordConfig{
|
2021-12-15 04:47:38 +08:00
|
|
|
TTL: uint32(entry.Expire),
|
2021-07-07 23:43:10 +08:00
|
|
|
Type: entry.Type,
|
|
|
|
Original: entry,
|
|
|
|
}
|
|
|
|
rc.SetLabel(entry.Name, origin)
|
2023-12-11 09:47:44 +08:00
|
|
|
if err := rc.PopulateFromStringFunc(entry.Type, entry.Content, origin, nil); err != nil {
|
2021-07-07 23:43:10 +08:00
|
|
|
return nil, fmt.Errorf("unparsable record received from TransIP: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeOtherNS(dc *models.DomainConfig) {
|
|
|
|
newList := make([]*models.RecordConfig, 0, len(dc.Records))
|
|
|
|
for _, rec := range dc.Records {
|
|
|
|
if rec.Type == "NS" && (strings.HasPrefix(rec.GetTargetField(), "ns0.transip") ||
|
|
|
|
strings.HasPrefix(rec.GetTargetField(), "ns1.transip") ||
|
|
|
|
strings.HasPrefix(rec.GetTargetField(), "ns2.transip")) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
newList = append(newList, rec)
|
|
|
|
}
|
|
|
|
dc.Records = newList
|
|
|
|
}
|