From 24a3399f9c5ff5a8f57d885b71f39d47a1ab8424 Mon Sep 17 00:00:00 2001 From: GoEdgeLab Date: Thu, 23 Nov 2023 15:15:11 +0800 Subject: [PATCH] =?UTF-8?q?DNSPod=E6=94=B9=E5=90=8D=E4=B8=BA=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E4=BA=91DNSPod/DNSPod=20=E6=94=AF=E6=8C=81=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E4=BA=91API=E5=AF=86=E9=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/dnsclients/provider_dnspod.go | 65 +++- internal/dnsclients/provider_tencent_dns.go | 409 ++++++++++++++++++++ internal/dnsclients/types.go | 2 +- internal/dnsclients/types_ext.go | 4 + 4 files changed, 469 insertions(+), 11 deletions(-) create mode 100644 internal/dnsclients/provider_tencent_dns.go diff --git a/internal/dnsclients/provider_dnspod.go b/internal/dnsclients/provider_dnspod.go index 496e75b0..b184151b 100644 --- a/internal/dnsclients/provider_dnspod.go +++ b/internal/dnsclients/provider_dnspod.go @@ -32,8 +32,6 @@ var dnsPodHTTPClient = &http.Client{ } // DNSPodProvider DNSPod服务商 -// TODO 考虑支持线路ID -// TODO 支持自定义线路 type DNSPodProvider struct { BaseProvider @@ -42,19 +40,30 @@ type DNSPodProvider struct { region string apiId string apiToken string + + tencentDNSProvider *TencentDNSProvider } // Auth 认证 func (this *DNSPodProvider) Auth(params maps.Map) error { - this.apiId = params.GetString("id") - this.apiToken = params.GetString("token") - this.region = params.GetString("region") + // 兼容腾讯云API + var apiType = params.GetString("apiType") - if len(this.apiId) == 0 { - return errors.New("'id' should be not empty") - } - if len(this.apiToken) == 0 { - return errors.New("'token' should not be empty") + switch apiType { + case "tencentDNS": + this.tencentDNSProvider = NewTencentDNSProvider() + return this.tencentDNSProvider.Auth(params) + default: + this.apiId = params.GetString("id") + this.apiToken = params.GetString("token") + this.region = params.GetString("region") + + if len(this.apiId) == 0 { + return errors.New("'id' should be not empty") + } + if len(this.apiToken) == 0 { + return errors.New("'token' should not be empty") + } } return nil @@ -62,6 +71,10 @@ func (this *DNSPodProvider) Auth(params maps.Map) error { // GetDomains 获取所有域名列表 func (this *DNSPodProvider) GetDomains() (domains []string, err error) { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.GetDomains() + } + var offset = 0 var size = 3000 @@ -92,6 +105,10 @@ func (this *DNSPodProvider) GetDomains() (domains []string, err error) { // GetRecords 获取域名列表 func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.GetRecords(domain) + } + var offset = 0 var size = 3000 for { @@ -135,6 +152,10 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor // GetRoutes 读取线路数据 func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.GetRoutes(domain) + } + var domainInfoResp = new(dnspod.DomainInfoResponse) err = this.doAPI("/Domain.Info", map[string]string{ "domain": domain, @@ -217,6 +238,10 @@ func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route, // QueryRecord 查询单个记录 func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.QueryRecord(domain, name, recordType) + } + // 从缓存中读取 if this.ProviderId > 0 { record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType) @@ -239,6 +264,10 @@ func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType d // QueryRecords 查询多个记录 func (this *DNSPodProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.QueryRecords(domain, name, recordType) + } + // 从缓存中读取 if this.ProviderId > 0 { records, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecords(this.ProviderId, domain, name, recordType) @@ -262,6 +291,10 @@ func (this *DNSPodProvider) QueryRecords(domain string, name string, recordType // AddRecord 设置记录 func (this *DNSPodProvider) AddRecord(domain string, newRecord *dnstypes.Record) error { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.AddRecord(domain, newRecord) + } + if newRecord == nil { return errors.New("invalid new record") } @@ -298,6 +331,10 @@ func (this *DNSPodProvider) AddRecord(domain string, newRecord *dnstypes.Record) // UpdateRecord 修改记录 func (this *DNSPodProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.UpdateRecord(domain, record, newRecord) + } + if record == nil { return errors.New("invalid record") } @@ -339,6 +376,10 @@ func (this *DNSPodProvider) UpdateRecord(domain string, record *dnstypes.Record, // DeleteRecord 删除记录 func (this *DNSPodProvider) DeleteRecord(domain string, record *dnstypes.Record) error { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.DeleteRecord(domain, record) + } + if record == nil { return errors.New("invalid record to delete") } @@ -412,6 +453,10 @@ func (this *DNSPodProvider) doAPI(path string, params map[string]string, respPtr // DefaultRoute 默认线路 func (this *DNSPodProvider) DefaultRoute() string { + if this.tencentDNSProvider != nil { + return this.tencentDNSProvider.DefaultRoute() + } + if this.isInternational() { return "Default" } diff --git a/internal/dnsclients/provider_tencent_dns.go b/internal/dnsclients/provider_tencent_dns.go new file mode 100644 index 00000000..caaaf2e6 --- /dev/null +++ b/internal/dnsclients/provider_tencent_dns.go @@ -0,0 +1,409 @@ +// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package dnsclients + +import ( + "errors" + "github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes" + "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/types" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + tencenterrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323" + "strings" +) + +// TencentDNSProvider 腾讯云DNS云解析 +type TencentDNSProvider struct { + BaseProvider + + ProviderId int64 + + client *dnspod.Client +} + +func NewTencentDNSProvider() *TencentDNSProvider { + return &TencentDNSProvider{} +} + +// Auth 认证 +func (this *TencentDNSProvider) Auth(params maps.Map) error { + var accessKeyId = params.GetString("accessKeyId") + var accessKeySecret = params.GetString("accessKeySecret") + if len(accessKeyId) == 0 { + return errors.New("'accessKeyId' required") + } + if len(accessKeySecret) == 0 { + return errors.New("'accessKeySecret' required") + } + + client, err := dnspod.NewClient(common.NewCredential(accessKeyId, accessKeySecret), "", profile.NewClientProfile()) + if err != nil { + return err + } + this.client = client + + return nil +} + +// GetDomains 获取所有域名列表 +func (this *TencentDNSProvider) GetDomains() (domains []string, err error) { + var offset int64 = 0 + var limit int64 = 1000 + for { + var req = dnspod.NewDescribeDomainListRequest() + req.Offset = this.int64Val(offset) + req.Limit = this.int64Val(limit) + resp, respErr := this.client.DescribeDomainList(req) + if respErr != nil { + if this.isNotFoundErr(respErr) { + break + } + return nil, respErr + } + var countDomains = len(resp.Response.DomainList) + if countDomains == 0 { + break + } + for _, domainObj := range resp.Response.DomainList { + domains = append(domains, *domainObj.Name) + } + offset += int64(countDomains) + } + + return +} + +// GetRecords 获取域名列表 +func (this *TencentDNSProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) { + var offset uint64 = 0 + var limit uint64 = 1000 + for { + var req = dnspod.NewDescribeRecordListRequest() + req.Domain = this.stringVal(domain) + req.Offset = this.uint64Val(offset) + req.Limit = this.uint64Val(limit) + resp, respErr := this.client.DescribeRecordList(req) + if respErr != nil { + if this.isNotFoundErr(respErr) { + break + } + return nil, respErr + } + var countRecords = len(resp.Response.RecordList) + if countRecords == 0 { + break + } + for _, recordObj := range resp.Response.RecordList { + records = append(records, &dnstypes.Record{ + Id: types.String(*recordObj.RecordId), + Name: *recordObj.Name, + Type: *recordObj.Type, + Value: this.fixCNAME(*recordObj.Type, *recordObj.Value), + Route: *recordObj.LineId, + TTL: types.Int32(*recordObj.TTL), + }) + } + offset += uint64(countRecords) + } + + // 写入缓存 + if this.ProviderId > 0 { + sharedDomainRecordsCache.WriteDomainRecords(this.ProviderId, domain, records) + } + + return +} + +// GetRoutes 读取线路数据 +func (this *TencentDNSProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) { + // 等级信息 + var domainGrade string + { + var req = dnspod.NewDescribeDomainRequest() + req.Domain = this.stringVal(domain) + resp, respErr := this.client.DescribeDomain(req) + if respErr != nil { + if this.isNotFoundErr(respErr) { + return + } + return nil, respErr + } + if resp.Response.DomainInfo == nil { + return + } + domainGrade = *resp.Response.DomainInfo.Grade + } + + // 等级允许的线路 + { + var req = dnspod.NewDescribeRecordLineListRequest() + req.Domain = this.stringVal(domain) + req.DomainGrade = this.stringVal(domainGrade) + resp, respErr := this.client.DescribeRecordLineList(req) + if respErr != nil { + return nil, respErr + } + for _, lineGroupObj := range resp.Response.LineGroupList { + routes = append(routes, &dnstypes.Route{ + Name: "Group:" + *lineGroupObj.Name, + Code: *lineGroupObj.LineId, + }) + } + for _, lineObj := range resp.Response.LineList { + routes = append(routes, &dnstypes.Route{ + Name: *lineObj.Name, + Code: *lineObj.LineId, + }) + } + } + + return +} + +// QueryRecord 查询单个记录 +func (this *TencentDNSProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) { + // 从缓存中读取 + if this.ProviderId > 0 { + record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType) + if hasRecords { // 有效的搜索 + return record, nil + } + } + + var offset uint64 = 0 + var limit uint64 = 1000 + var req = dnspod.NewDescribeRecordFilterListRequest() + req.Domain = this.stringVal(domain) + req.Offset = this.uint64Val(offset) + req.Limit = this.uint64Val(limit) + req.SubDomain = this.stringVal(name) + req.RecordType = []*string{this.stringVal(recordType)} + resp, respErr := this.client.DescribeRecordFilterList(req) + if respErr != nil { + if this.isNotFoundErr(respErr) { + return nil, nil + } + return nil, respErr + } + var countRecords = len(resp.Response.RecordList) + if countRecords == 0 { + return nil, nil + } + for _, recordObj := range resp.Response.RecordList { + if *recordObj.Name == name && *recordObj.Type == recordType { + return &dnstypes.Record{ + Id: types.String(*recordObj.RecordId), + Name: *recordObj.Name, + Type: *recordObj.Type, + Value: this.fixCNAME(*recordObj.Type, *recordObj.Value), + Route: *recordObj.LineId, + TTL: types.Int32(*recordObj.TTL), + }, nil + } + } + + return nil, nil +} + +// QueryRecords 查询多个记录 +func (this *TencentDNSProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) { + // 从缓存中读取 + if this.ProviderId > 0 { + records, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecords(this.ProviderId, domain, name, recordType) + if hasRecords { // 有效的搜索 + return records, nil + } + } + + var offset uint64 = 0 + var limit uint64 = 1000 + var records []*dnstypes.Record + for { + var req = dnspod.NewDescribeRecordFilterListRequest() + req.Domain = this.stringVal(domain) + req.Offset = this.uint64Val(offset) + req.Limit = this.uint64Val(limit) + req.SubDomain = this.stringVal(name) + req.RecordType = []*string{this.stringVal(recordType)} + resp, respErr := this.client.DescribeRecordFilterList(req) + if respErr != nil { + if this.isNotFoundErr(respErr) { + break + } + return nil, respErr + } + var countRecords = len(resp.Response.RecordList) + if countRecords == 0 { + break + } + for _, recordObj := range resp.Response.RecordList { + records = append(records, &dnstypes.Record{ + Id: types.String(*recordObj.RecordId), + Name: *recordObj.Name, + Type: *recordObj.Type, + Value: this.fixCNAME(*recordObj.Type, *recordObj.Value), + Route: *recordObj.LineId, + TTL: types.Int32(*recordObj.TTL), + }) + } + offset += uint64(countRecords) + } + + return records, nil +} + +// AddRecord 设置记录 +func (this *TencentDNSProvider) AddRecord(domain string, newRecord *dnstypes.Record) error { + if newRecord == nil { + return errors.New("invalid new record") + } + + // 在CHANGE记录后面加入点 + if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") { + newRecord.Value += "." + } + + var ttl = newRecord.TTL + if ttl <= 0 { + ttl = 600 + } + + var req = dnspod.NewCreateRecordRequest() + req.Domain = this.stringVal(domain) + req.SubDomain = this.stringVal(newRecord.Name) + req.RecordType = this.stringVal(newRecord.Type) + req.TTL = this.uint64Val(uint64(ttl)) + req.RecordLine = this.stringVal(this.DefaultRouteName()) // 默认必填项,但以RecordLineId优先 + req.RecordLineId = this.stringVal(newRecord.Route) + req.Value = this.stringVal(newRecord.Value) + resp, respErr := this.client.CreateRecord(req) + if respErr != nil { + return respErr + } + newRecord.Id = types.String(*resp.Response.RecordId) + + // 加入缓存 + if this.ProviderId > 0 { + sharedDomainRecordsCache.AddDomainRecord(this.ProviderId, domain, newRecord) + } + + return nil +} + +// UpdateRecord 修改记录 +func (this *TencentDNSProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error { + if record == nil { + return errors.New("invalid record") + } + if newRecord == nil { + return errors.New("invalid new record") + } + + // 在CHANGE记录后面加入点 + if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") { + newRecord.Value += "." + } + + var newRoute = newRecord.Route + if len(newRoute) == 0 { + newRoute = this.DefaultRoute() + } + + var ttl = newRecord.TTL + if ttl <= 0 { + ttl = 600 + } + + var req = dnspod.NewModifyRecordRequest() + req.Domain = this.stringVal(domain) + req.RecordId = this.uint64Val(types.Uint64(record.Id)) + req.SubDomain = this.stringVal(newRecord.Name) + req.RecordType = this.stringVal(newRecord.Type) + req.TTL = this.uint64Val(uint64(ttl)) + req.RecordLine = this.stringVal(this.DefaultRouteName()) // 默认必填项,但以RecordLineId优先 + req.RecordLineId = this.stringVal(newRecord.Route) + req.Value = this.stringVal(newRecord.Value) + _, respErr := this.client.ModifyRecord(req) + if respErr != nil { + return respErr + } + + // 修改缓存 + if this.ProviderId > 0 { + sharedDomainRecordsCache.UpdateDomainRecord(this.ProviderId, domain, newRecord) + } + + return nil +} + +// DeleteRecord 删除记录 +func (this *TencentDNSProvider) DeleteRecord(domain string, record *dnstypes.Record) error { + if record == nil { + return errors.New("invalid record to delete") + } + + var req = dnspod.NewDeleteRecordRequest() + req.Domain = this.stringVal(domain) + req.RecordId = this.uint64Val(types.Uint64(record.Id)) + _, respErr := this.client.DeleteRecord(req) + if respErr != nil { + if len(record.Id) > 0 && this.isRecordInvalidErr(respErr) { + return nil + } + return respErr + } + + // 删除缓存 + if this.ProviderId > 0 { + sharedDomainRecordsCache.DeleteDomainRecord(this.ProviderId, domain, record.Id) + } + + return nil +} + +// DefaultRoute 默认线路 +func (this *TencentDNSProvider) DefaultRoute() string { + return "0" +} + +func (this *TencentDNSProvider) DefaultRouteName() string { + return "默认" +} + +func (this *TencentDNSProvider) fixCNAME(recordType string, recordValue string) string { + // 修正Record + if strings.ToUpper(recordType) == dnstypes.RecordTypeCNAME && !strings.HasSuffix(recordValue, ".") { + recordValue += "." + } + return recordValue +} + +func (this *TencentDNSProvider) int64Val(v int64) *int64 { + return &v +} + +func (this *TencentDNSProvider) uint64Val(v uint64) *uint64 { + return &v +} + +func (this *TencentDNSProvider) stringVal(s string) *string { + return &s +} + +func (this *TencentDNSProvider) isNotFoundErr(err error) bool { + if err == nil { + return false + } + var sdkErr *tencenterrors.TencentCloudSDKError + return errors.As(err, &sdkErr) && strings.HasPrefix(sdkErr.Code, "ResourceNotFound.") +} + +func (this *TencentDNSProvider) isRecordInvalidErr(err error) bool { + if err == nil { + return false + } + var sdkErr *tencenterrors.TencentCloudSDKError + return errors.As(err, &sdkErr) && sdkErr.Code == "InvalidParameter.RecordIdInvalid" +} diff --git a/internal/dnsclients/types.go b/internal/dnsclients/types.go index 2115ba6d..d9a5afb6 100644 --- a/internal/dnsclients/types.go +++ b/internal/dnsclients/types.go @@ -26,7 +26,7 @@ func FindAllProviderTypes() []maps.Map { "description": "阿里云提供的DNS服务。", }, { - "name": "DNSPod", + "name": "腾讯云DNSPod", "code": ProviderTypeDNSPod, "description": "DNSPod提供的DNS服务。", }, diff --git a/internal/dnsclients/types_ext.go b/internal/dnsclients/types_ext.go index 44154b88..ce9b5d97 100644 --- a/internal/dnsclients/types_ext.go +++ b/internal/dnsclients/types_ext.go @@ -10,6 +10,10 @@ import ( // FindProvider 查找服务商实例 func FindProvider(providerType ProviderType, providerId int64) ProviderInterface { switch providerType { + case ProviderTypeTencentDNS: + return &TencentDNSProvider{ + ProviderId: providerId, + } case ProviderTypeDNSPod: return &DNSPodProvider{ ProviderId: providerId,