1.4.5.2
This commit is contained in:
417
EdgeAPI/internal/dnsclients/provider_bunny_net_plus.go
Normal file
417
EdgeAPI/internal/dnsclients/provider_bunny_net_plus.go
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package dnsclients
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/bunnynet"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const BunnyNetAPIEndpoint = "https://api.bunny.net/"
|
||||
const BunnyNetDefaultRoute = "default"
|
||||
|
||||
var bunnyNetHTTPClient = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
/**Proxy: func(req *http.Request) (*url.URL, error) {
|
||||
return url.Parse("socks5://127.0.0.1:7890")
|
||||
},**/
|
||||
},
|
||||
}
|
||||
|
||||
type BunnyNetProvider struct {
|
||||
BaseProvider
|
||||
|
||||
ProviderId int64
|
||||
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// Auth 认证
|
||||
func (this *BunnyNetProvider) Auth(params maps.Map) error {
|
||||
this.apiKey = params.GetString("apiKey")
|
||||
if len(this.apiKey) == 0 {
|
||||
return errors.New("'apiKey' should not be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaskParams 对参数进行掩码
|
||||
func (this *BunnyNetProvider) MaskParams(params maps.Map) {
|
||||
if params == nil {
|
||||
return
|
||||
}
|
||||
params["apiKey"] = MaskString(params.GetString("apiKey"))
|
||||
}
|
||||
|
||||
// GetDomains 获取所有域名列表
|
||||
func (this *BunnyNetProvider) GetDomains() (domains []string, err error) {
|
||||
for i := 1; i <= 1000; i++ {
|
||||
var resp = new(bunnynet.ListDNSZonesResponse)
|
||||
err = this.doAPI(http.MethodGet, "/dnszone?perPage=1000&page="+types.String(i), nil, nil, resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, item := range resp.Items {
|
||||
domains = append(domains, item.Domain)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetRecords 获取域名解析记录列表
|
||||
func (this *BunnyNetProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
|
||||
if len(domain) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, record := range domainResp.Records {
|
||||
var recordType = this.recordTypeString(record.Type)
|
||||
if recordType == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Value, ".") {
|
||||
record.Value += "."
|
||||
}
|
||||
|
||||
if len(record.LatencyZone) == 0 {
|
||||
record.LatencyZone = BunnyNetDefaultRoute
|
||||
}
|
||||
|
||||
records = append(records, &dnstypes.Record{
|
||||
Id: types.String(record.Id),
|
||||
Name: record.Name,
|
||||
Type: recordType,
|
||||
Value: record.Value,
|
||||
Route: record.LatencyZone,
|
||||
TTL: record.Ttl,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetRoutes 读取域名支持的线路数据
|
||||
func (this *BunnyNetProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
|
||||
if len(domain) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var resp = new(bunnynet.RegionListResponse)
|
||||
err = this.doAPI(http.MethodGet, "/region", nil, nil, resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, region := range *resp {
|
||||
routes = append(routes, &dnstypes.Route{
|
||||
Name: region.Name,
|
||||
Code: region.RegionCode,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// QueryRecord 查询单个记录
|
||||
func (this *BunnyNetProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
|
||||
if len(domain) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, record := range domainResp.Records {
|
||||
if record.Name == name && this.recordTypeString(record.Type) == recordType {
|
||||
if recordType == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Value, ".") {
|
||||
record.Value += "."
|
||||
}
|
||||
|
||||
if len(record.LatencyZone) == 0 {
|
||||
record.LatencyZone = BunnyNetDefaultRoute
|
||||
}
|
||||
|
||||
return &dnstypes.Record{
|
||||
Id: types.String(record.Id),
|
||||
Name: record.Name,
|
||||
Type: recordType,
|
||||
Value: record.Value,
|
||||
Route: record.LatencyZone,
|
||||
TTL: record.Ttl,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// QueryRecords 查询多个记录
|
||||
func (this *BunnyNetProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) {
|
||||
if len(domain) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resultRecords []*dnstypes.Record
|
||||
for _, record := range domainResp.Records {
|
||||
if record.Name == name && this.recordTypeString(record.Type) == recordType {
|
||||
if recordType == dnstypes.RecordTypeCNAME && !strings.HasSuffix(record.Value, ".") {
|
||||
record.Value += "."
|
||||
}
|
||||
|
||||
if len(record.LatencyZone) == 0 {
|
||||
record.LatencyZone = BunnyNetDefaultRoute
|
||||
}
|
||||
|
||||
resultRecords = append(resultRecords, &dnstypes.Record{
|
||||
Id: types.String(record.Id),
|
||||
Name: record.Name,
|
||||
Type: recordType,
|
||||
Value: record.Value,
|
||||
Route: record.LatencyZone,
|
||||
TTL: record.Ttl,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return resultRecords, nil
|
||||
}
|
||||
|
||||
// AddRecord 设置记录
|
||||
func (this *BunnyNetProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newRecord.Route == BunnyNetDefaultRoute {
|
||||
newRecord.Route = ""
|
||||
}
|
||||
|
||||
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
|
||||
newRecord.Value += "."
|
||||
}
|
||||
|
||||
var recordMap = maps.Map{
|
||||
"Type": this.recordTypeInt(newRecord.Type),
|
||||
"Ttl": newRecord.TTL,
|
||||
"Value": newRecord.Value,
|
||||
"Name": newRecord.Name,
|
||||
}
|
||||
|
||||
if len(newRecord.Route) > 0 {
|
||||
recordMap["SmartRoutingType"] = 1
|
||||
recordMap["LatencyZone"] = newRecord.Route
|
||||
}
|
||||
|
||||
var resp = new(bunnynet.RecordResponse)
|
||||
err = this.doAPI(http.MethodPut, "/dnszone/"+types.String(domainResp.Id)+"/records", nil, recordMap, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRecord.Id = types.String(resp.Id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRecord 修改记录
|
||||
func (this *BunnyNetProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newRecord.Route == BunnyNetDefaultRoute {
|
||||
newRecord.Route = ""
|
||||
}
|
||||
|
||||
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
|
||||
newRecord.Value += "."
|
||||
}
|
||||
|
||||
var recordMap = maps.Map{
|
||||
"Type": this.recordTypeInt(newRecord.Type),
|
||||
"Ttl": newRecord.TTL,
|
||||
"Value": newRecord.Value,
|
||||
"Name": newRecord.Name,
|
||||
}
|
||||
|
||||
if len(newRecord.Route) > 0 {
|
||||
recordMap["SmartRoutingType"] = 1
|
||||
recordMap["LatencyZone"] = newRecord.Route
|
||||
}
|
||||
|
||||
err = this.doAPI(http.MethodPost, "/dnszone/"+types.String(domainResp.Id)+"/records/"+record.Id, nil, recordMap, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRecord 删除记录
|
||||
func (this *BunnyNetProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
|
||||
domainResp, err := this.findDomain(domain)
|
||||
if err != nil || domainResp == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.doAPI(http.MethodDelete, "/dnszone/"+types.String(domainResp.Id)+"/records/"+record.Id, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (this *BunnyNetProvider) findDomain(domainName string) (*bunnynet.DNSZoneResponse, error) {
|
||||
for i := 1; i <= 1000; i++ {
|
||||
var resp = new(bunnynet.ListDNSZonesResponse)
|
||||
err := this.doAPI(http.MethodGet, "/dnszone?search="+domainName+"&perPage=1000&page="+types.String(i), nil, nil, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp.Items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, item := range resp.Items {
|
||||
if item.Domain == domainName {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DefaultRoute 默认线路
|
||||
func (this *BunnyNetProvider) DefaultRoute() string {
|
||||
return BunnyNetDefaultRoute
|
||||
}
|
||||
|
||||
func (this *BunnyNetProvider) doAPI(method string, apiPath string, args map[string]string, bodyMap maps.Map, respPtr any) error {
|
||||
var apiURL = BunnyNetAPIEndpoint + strings.TrimLeft(apiPath, "/")
|
||||
if len(args) > 0 {
|
||||
apiURL += "?"
|
||||
argStrings := []string{}
|
||||
for k, v := range args {
|
||||
argStrings = append(argStrings, k+"="+url.QueryEscape(v))
|
||||
}
|
||||
apiURL += strings.Join(argStrings, "&")
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
var bodyReader io.Reader = nil
|
||||
if bodyMap != nil {
|
||||
bodyData, err := json.Marshal(bodyMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bodyReader = bytes.NewReader(bodyData)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, apiURL, bodyReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("AccessKey", this.apiKey)
|
||||
resp, err := bunnyNetHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if respPtr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 0 {
|
||||
return errors.New("invalid response status '" + strconv.Itoa(resp.StatusCode) + "', response '" + string(data) + "'")
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK &&
|
||||
resp.StatusCode != http.StatusCreated &&
|
||||
resp.StatusCode != http.StatusNoContent {
|
||||
return errors.New("response error: " + string(data))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, respPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode json failed: %w, response text: %s", err, string(data))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BunnyNetProvider) recordTypeString(recordType int) dnstypes.RecordType {
|
||||
switch recordType {
|
||||
case 0:
|
||||
return dnstypes.RecordTypeA
|
||||
case 1:
|
||||
return dnstypes.RecordTypeAAAA
|
||||
case 2:
|
||||
return dnstypes.RecordTypeCNAME
|
||||
case 3:
|
||||
return dnstypes.RecordTypeTXT
|
||||
default:
|
||||
// other record types has been not supported yet
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BunnyNetProvider) recordTypeInt(recordType string) int {
|
||||
switch recordType {
|
||||
case dnstypes.RecordTypeA:
|
||||
return 0
|
||||
case dnstypes.RecordTypeAAAA:
|
||||
return 1
|
||||
case dnstypes.RecordTypeCNAME:
|
||||
return 2
|
||||
case dnstypes.RecordTypeTXT:
|
||||
return 3
|
||||
default:
|
||||
// other record types has been not supported yet
|
||||
return -1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user