mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-09 05:36:27 +08:00
181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package alidns
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
|
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
|
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
|
|
)
|
|
|
|
var features = providers.DocumentationNotes{
|
|
providers.CanUseAlias: providers.Cannot(),
|
|
providers.CanUseCAA: providers.Can(),
|
|
providers.CanUsePTR: providers.Cannot(),
|
|
providers.CanUseNAPTR: providers.Cannot(),
|
|
providers.CanUseSRV: providers.Can(),
|
|
providers.CanUseSSHFP: providers.Cannot(),
|
|
providers.CanUseTLSA: providers.Cannot(),
|
|
providers.CanAutoDNSSEC: providers.Can(),
|
|
providers.CanConcur: providers.Cannot(),
|
|
|
|
providers.DocOfficiallySupported: providers.Cannot(),
|
|
providers.DocDualHost: providers.Cannot(),
|
|
providers.DocCreateDomains: providers.Cannot(),
|
|
|
|
providers.CanUseRoute53Alias: providers.Cannot(),
|
|
}
|
|
|
|
func init() {
|
|
const providerName = "ALIDNS"
|
|
const providerMaintainer = "@bytemain"
|
|
fns := providers.DspFuncs{
|
|
Initializer: newAliDnsDsp,
|
|
RecordAuditor: AuditRecords,
|
|
}
|
|
providers.RegisterDomainServiceProviderType(providerName, fns, features)
|
|
// https://www.alibabacloud.com/help/en/dns/pubz-add-parsing-record#45347620b7mi9
|
|
// Explicit URL forwarding uses 301 (permanent redirect) or 302 (temporary redirect)
|
|
// redirection technology. The browser's address bar displays the target address, and the content displayed is from the target website.
|
|
providers.RegisterCustomRecordType("EXPLICIT_URL_FORWARDING", providerName, "")
|
|
// Implicit URL forwarding: Implicit URL Forwarding forwarding uses iframe technology.
|
|
// The domain name in the browser's address bar does not change, but the content displayed is from the target website.
|
|
providers.RegisterCustomRecordType("IMPLICIT_URL_FORWARDING", providerName, "")
|
|
providers.RegisterMaintainer(providerName, providerMaintainer)
|
|
|
|
}
|
|
|
|
type aliDnsDsp struct {
|
|
client *alidns.Client
|
|
}
|
|
|
|
func newAliDnsDsp(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
|
accessKeyID := config["access_key_id"]
|
|
if accessKeyID == "" {
|
|
return nil, fmt.Errorf("creds.json: access_key_id must not be empty")
|
|
}
|
|
|
|
accessKeySecret := config["access_key_secret"]
|
|
if accessKeySecret == "" {
|
|
return nil, fmt.Errorf("creds.json: access_key_secret must not be empty")
|
|
}
|
|
|
|
// Region ID defaults to "cn-hangzhou". The region value does not affect
|
|
// DNS management (DNS is global) but Alibaba's SDK/examples require a
|
|
// region to be provided — their docs/examples use Hangzhou:
|
|
// https://www.alibabacloud.com/help/en/dns/quick-start-1
|
|
region := config["region_id"]
|
|
if region == "" {
|
|
region = "cn-hangzhou"
|
|
}
|
|
|
|
client, err := alidns.NewClientWithAccessKey(
|
|
region,
|
|
accessKeyID,
|
|
accessKeySecret,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &aliDnsDsp{client}, nil
|
|
}
|
|
|
|
// GetZoneRecords returns an array of RecordConfig structs for a zone.
|
|
func (a *aliDnsDsp) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
|
|
// Fetch all pages of domain records.
|
|
records, err := a.describeDomainRecordsAll(domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := models.Records{}
|
|
for _, r := range records {
|
|
if r.Status != "ENABLE" {
|
|
continue
|
|
}
|
|
|
|
rc, err := nativeToRecord(r, domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out = append(out, rc)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func removeTrailingDot(record string) string {
|
|
return strings.TrimSuffix(record, ".")
|
|
}
|
|
|
|
func deduplicateNameServerTargets(newRecs models.Records) models.Records {
|
|
dedupedMap := make(map[string]bool)
|
|
var deduped models.Records
|
|
for _, rec := range newRecs {
|
|
if !dedupedMap[rec.GetTargetField()] {
|
|
dedupedMap[rec.GetTargetField()] = true
|
|
deduped = append(deduped, rec)
|
|
}
|
|
}
|
|
return deduped
|
|
}
|
|
|
|
func (a *aliDnsDsp) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, int, error) {
|
|
var corrections []*models.Correction
|
|
|
|
// Azure is a "ByRecordSet" API.
|
|
changes, actualChangeCount, err := diff2.ByRecord(existingRecords, dc, nil)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
for _, change := range changes {
|
|
// Copy all param values to local variables to avoid overwrites
|
|
msgs := change.MsgsJoined
|
|
dcn := dc.Name
|
|
chaKey := change.Key
|
|
|
|
if change.Type == diff2.CHANGE || change.Type == diff2.CREATE {
|
|
if chaKey.Type == "NS" && dcn == removeTrailingDot(change.Key.NameFQDN) {
|
|
change.New = deduplicateNameServerTargets(change.New)
|
|
}
|
|
}
|
|
|
|
switch change.Type {
|
|
case diff2.REPORT:
|
|
corrections = append(corrections, &models.Correction{Msg: change.MsgsJoined})
|
|
case diff2.CREATE:
|
|
changeNew := change.New
|
|
corrections = append(corrections, &models.Correction{
|
|
Msg: msgs,
|
|
F: func() error {
|
|
return a.createRecordset(changeNew, dcn)
|
|
},
|
|
})
|
|
case diff2.CHANGE:
|
|
changeNew := change.New
|
|
changeExisting := change.Old
|
|
corrections = append(corrections, &models.Correction{
|
|
Msg: msgs,
|
|
F: func() error {
|
|
return a.updateRecordset(changeExisting, changeNew, dcn)
|
|
},
|
|
})
|
|
case diff2.DELETE:
|
|
corrections = append(corrections, &models.Correction{
|
|
Msg: msgs,
|
|
F: func() error {
|
|
return a.deleteRecordset(change.Old, dcn)
|
|
},
|
|
})
|
|
default:
|
|
panic(fmt.Sprintf("unhandled change.Type %s", change.Type))
|
|
}
|
|
}
|
|
|
|
return corrections, actualChangeCount, nil
|
|
}
|