202 lines
5.5 KiB
Go
202 lines
5.5 KiB
Go
package ddns
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/volcengine/volcengine-go-sdk/service/dns"
|
|
"github.com/volcengine/volcengine-go-sdk/volcengine"
|
|
"github.com/volcengine/volcengine-go-sdk/volcengine/credentials"
|
|
"github.com/volcengine/volcengine-go-sdk/volcengine/session"
|
|
"github.com/volcengine/volcengine-go-sdk/volcengine/volcengineerr"
|
|
)
|
|
|
|
const volcengineDNSRegion = "cn-beijing"
|
|
|
|
type Volcengine struct{}
|
|
|
|
func NewVolcengine() *Volcengine { return &Volcengine{} }
|
|
|
|
func (v *Volcengine) Name() string { return "volcengine" }
|
|
|
|
func (v *Volcengine) Verify(ctx context.Context, cred Credentials) error {
|
|
client, err := v.client(cred)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = client.ListZonesWithContext(ctx, &dns.ListZonesInput{
|
|
PageNumber: volcengine.Int32(1),
|
|
PageSize: volcengine.Int32(1),
|
|
})
|
|
return wrapVolcengineErr(err)
|
|
}
|
|
|
|
func (v *Volcengine) HasZone(ctx context.Context, cred Credentials, zone string) (bool, error) {
|
|
client, err := v.client(cred)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
_, err = v.zoneID(ctx, client, zone)
|
|
return zoneLookupOK(err)
|
|
}
|
|
|
|
func (v *Volcengine) GetRecordIP(ctx context.Context, cred Credentials, rootDomain, recordName, recordType string) (string, error) {
|
|
client, err := v.client(cred)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
zid, err := v.zoneID(ctx, client, rootDomain)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
rec, err := v.findRecord(ctx, client, zid, volcengineHost(recordName), recordType)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if rec != nil && rec.Value != nil {
|
|
return *rec.Value, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (v *Volcengine) UpdateRecord(ctx context.Context, cred Credentials, rootDomain, recordName, recordType, ip string) error {
|
|
client, err := v.client(cred)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
zid, err := v.zoneID(ctx, client, rootDomain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
host := volcengineHost(recordName)
|
|
existing, err := v.findRecord(ctx, client, zid, host, recordType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existing != nil && existing.RecordID != nil {
|
|
line := volcengineLine(existing)
|
|
_, err = client.UpdateRecordWithContext(ctx, &dns.UpdateRecordInput{
|
|
RecordID: existing.RecordID,
|
|
Host: volcengine.String(host),
|
|
Line: volcengine.String(line),
|
|
Type: volcengine.String(recordType),
|
|
Value: volcengine.String(ip),
|
|
TTL: volcengine.Int32(600),
|
|
})
|
|
return wrapVolcengineErr(err)
|
|
}
|
|
|
|
_, err = client.CreateRecordWithContext(ctx, &dns.CreateRecordInput{
|
|
ZID: volcengine.Int64(zid),
|
|
Host: volcengine.String(host),
|
|
Type: volcengine.String(recordType),
|
|
Value: volcengine.String(ip),
|
|
Line: volcengine.String("default"),
|
|
TTL: volcengine.Int32(600),
|
|
})
|
|
return wrapVolcengineErr(err)
|
|
}
|
|
|
|
func (v *Volcengine) client(cred Credentials) (*dns.DNS, error) {
|
|
if cred.Token == "" || cred.Secret == "" {
|
|
return nil, fmt.Errorf("火山引擎 DNS 凭证未配置")
|
|
}
|
|
cfg := volcengine.NewConfig().
|
|
WithRegion(volcengineDNSRegion).
|
|
WithCredentials(credentials.NewStaticCredentials(cred.Token, cred.Secret, ""))
|
|
sess, err := session.NewSession(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dns.New(sess), nil
|
|
}
|
|
|
|
func (v *Volcengine) zoneID(ctx context.Context, client *dns.DNS, rootDomain string) (int64, error) {
|
|
rootDomain = strings.ToLower(strings.TrimSpace(rootDomain))
|
|
var page int32 = 1
|
|
const pageSize int32 = 100
|
|
for {
|
|
out, err := client.ListZonesWithContext(ctx, &dns.ListZonesInput{
|
|
Key: volcengine.String(rootDomain),
|
|
PageNumber: volcengine.Int32(page),
|
|
PageSize: volcengine.Int32(pageSize),
|
|
})
|
|
if err != nil {
|
|
return 0, wrapVolcengineErr(err)
|
|
}
|
|
for _, zone := range out.Zones {
|
|
if zone.ZoneName != nil && strings.EqualFold(*zone.ZoneName, rootDomain) && zone.ZID != nil {
|
|
return int64(*zone.ZID), nil
|
|
}
|
|
}
|
|
total := int32(0)
|
|
if out.Total != nil {
|
|
total = *out.Total
|
|
}
|
|
if len(out.Zones) == 0 || page*pageSize >= total {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
return 0, fmt.Errorf("未找到域名 %s 对应的火山引擎 DNS 域名", rootDomain)
|
|
}
|
|
|
|
func (v *Volcengine) findRecord(ctx context.Context, client *dns.DNS, zid int64, host, recordType string) (*dns.RecordForListRecordsOutput, error) {
|
|
var page int32 = 1
|
|
const pageSize int32 = 100
|
|
for {
|
|
out, err := client.ListRecordsWithContext(ctx, &dns.ListRecordsInput{
|
|
ZID: volcengine.Int64(zid),
|
|
Host: volcengine.String(host),
|
|
Type: volcengine.String(recordType),
|
|
PageNumber: volcengine.Int32(page),
|
|
PageSize: volcengine.Int32(pageSize),
|
|
})
|
|
if err != nil {
|
|
return nil, wrapVolcengineErr(err)
|
|
}
|
|
for _, rec := range out.Records {
|
|
if rec.Host != nil && *rec.Host == host && rec.Type != nil && strings.EqualFold(*rec.Type, recordType) {
|
|
return rec, nil
|
|
}
|
|
}
|
|
total := int32(0)
|
|
if out.TotalCount != nil {
|
|
total = *out.TotalCount
|
|
}
|
|
if len(out.Records) == 0 || page*pageSize >= total {
|
|
break
|
|
}
|
|
page++
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func volcengineHost(recordName string) string {
|
|
return subDomainForRecord(recordName)
|
|
}
|
|
|
|
func volcengineLine(rec *dns.RecordForListRecordsOutput) string {
|
|
if rec != nil && rec.Line != nil && strings.TrimSpace(*rec.Line) != "" {
|
|
return *rec.Line
|
|
}
|
|
return "default"
|
|
}
|
|
|
|
func wrapVolcengineErr(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
var sdkErr volcengineerr.RequestFailure
|
|
if errors.As(err, &sdkErr) {
|
|
return fmt.Errorf("火山引擎 DNS API 错误:%s", sdkErr.Message())
|
|
}
|
|
var apiErr volcengineerr.Error
|
|
if errors.As(err, &apiErr) {
|
|
return fmt.Errorf("火山引擎 DNS API 错误:%s", apiErr.Message())
|
|
}
|
|
return err
|
|
}
|