diff --git a/README.md b/README.md index a9cd158..cc074e3 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ record_cert_info{ - [x] Tencent DnsPod - [x] Aliyun Dns - [x] Godaddy +- [x] DNSLA ## Grafana 仪表板 diff --git a/config.example.yaml b/config.example.yaml index ed0d2a8..771b579 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -20,4 +20,9 @@ cloud_providers: - name: g1 secretId: "xxxxx" secretKey: "xxxxx" - # 目前支持Tencent, Aliyun, Godaddy,如需支持更多云厂商,请提交 issue,也欢迎 PR \ No newline at end of file + dnsla: + accounts: + - name: d1 + secretId: "xxxxx" + secretKey: "xxxxx" + # 目前支持Tencent, Aliyun, Godaddy,DNALA,如需支持更多云厂商,请提交 issue,也欢迎 PR \ No newline at end of file diff --git a/dnslib/dnsla/client.go b/dnslib/dnsla/client.go new file mode 100644 index 0000000..4d1f58f --- /dev/null +++ b/dnslib/dnsla/client.go @@ -0,0 +1,37 @@ +package dnsla + +import ( + "errors" + "time" + + "github.com/go-resty/resty/v2" +) + +// Client DNS.LA 客户端 +type Client struct { + client *resty.Client + + // Services + Domains *DomainService + Records *RecordService +} + +var baseUrl = "https://api.dns.la" + +// NewClient 初始化客户端 +func NewClient(key, secret string) (*Client, error) { + c := new(Client) + if key == "" { + return c, errors.New("missing dns.la API key") + } + if secret == "" { + return c, errors.New("missing dns.la API secret") + } + c.client = resty.New().SetBaseURL(baseUrl).SetBasicAuth(key, secret). + SetTimeout(3 * time.Second).SetRetryCount(3).SetRetryWaitTime(2 * time.Second) + // Initialize services + c.Domains = &DomainService{c} + c.Records = &RecordService{c} + + return c, nil +} diff --git a/dnslib/dnsla/domain.go b/dnslib/dnsla/domain.go new file mode 100644 index 0000000..df3f1c4 --- /dev/null +++ b/dnslib/dnsla/domain.go @@ -0,0 +1,38 @@ +package dnsla + +import ( + "fmt" + "net/url" + "strconv" +) + +// DomainService 域名服务 +type DomainService struct{ *Client } + +// List 获取域名列表 +func (d *DomainService) List(page PageOption, options ...DomainListOption) (*DomainListResponse, error) { + params := url.Values{} + params.Set("pageIndex", strconv.Itoa(page.PageIndex)) + params.Set("pageSize", strconv.Itoa(page.PageSize)) + for _, option := range options { + option(params) + } + resp, err := d.client.R(). + SetQueryParamsFromValues(params). + SetResult(&DomainListResponse{}). + Get("/api/domainList") + if err != nil { + return nil, err + } + + if resp.IsError() { + return nil, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode(), resp.String()) + } + + result, ok := resp.Result().(*DomainListResponse) + if !ok { + return nil, fmt.Errorf("failed to cast response to *Response") + } + + return result, nil +} diff --git a/dnslib/dnsla/models.go b/dnslib/dnsla/models.go new file mode 100644 index 0000000..f22dbc9 --- /dev/null +++ b/dnslib/dnsla/models.go @@ -0,0 +1,177 @@ +package dnsla + +import ( + "net/url" + "strconv" +) + +// DomainResponse 解析记录列表响应 +type DomainListResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Total int `json:"total"` + Results []Domain `json:"results"` + } `json:"data"` +} + +// Domain 域名 +type Domain struct { + ID string `json:"id"` // 域名ID + CreatedAt int64 `json:"createdAt"` // 域名添加时间 Unix 时间戳 + UpdatedAt int64 `json:"updatedAt"` // 域名最后修改时间 Unix 时间戳 + UserID string `json:"userId"` // 用户ID + UserAccount string `json:"userAccount"` // 用户账号 + AssetID string `json:"assetId"` // 域名当前生效套餐的资产ID + GroupID string `json:"groupId"` // 分组ID,空为默认分组 + GroupName string `json:"groupName"` // 分组名称,空为默认分组 + Domain string `json:"domain"` // Punycode 编码后的域名 + DisplayDomain string `json:"displayDomain"` // Punycode 编码前的域名 + State int `json:"state"` // 域名状态 1 正常 | 2 暂停 其他状态咨询客服 + NsState int `json:"nsState"` // 域名NS状态 0 未知 | 1 匹配 | 2 未匹配 | 3 未加入 + NsCheckedAt int64 `json:"nsCheckedAt"` // 域名NS检查时间 + ProductCode string `json:"productCode"` // 域名套餐代码 + ProductName string `json:"productName"` // 域名套餐名称 + ExpiredAt int64 `json:"expiredAt"` // 过期时间 Unix 时间戳,免费域名过期时间 2100-01-01 + QuoteDomainID string `json:"quoteDomainId"` // 引用域名ID + QuoteDomain string `json:"quoteDomain"` // 引用域名 + Suffix string `json:"suffix"` // Punycode 编码后的顶级域名 + DisplaySuffix string `json:"displaySuffix"` // Punycode 编码前的顶级域名 +} + +// RecordListResponse 解析记录列表响应 +type RecordListResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Total int `json:"total"` + Results []Record `json:"results"` + } `json:"data"` +} + +// Record 解析记录 +type Record struct { + ID string `json:"id"` // 记录ID + CreatedAt int64 `json:"createdAt"` // 记录添加时间 Unix 时间戳 + UpdatedAt int64 `json:"updatedAt"` // 记录最后修改时间 Unix 时间戳 + DomainID string `json:"domainId"` // 域名ID + GroupID string `json:"groupId"` // 分组ID,空为默认分组 + GroupName string `json:"groupName"` // 分组名称,空为默认分组 + Host string `json:"host"` // Punycode 编码后的主机头 + DisplayHost string `json:"displayHost"` // Punycode 编码前的主机头 + Type int `json:"type"` // 记录类型 + LineID string `json:"lineId"` // 线路id,参考线路文档 + LineCode string `json:"lineCode"` // 线路code,参考线路文档 + LineName string `json:"lineName"` // 线路名称,参考线路文档 + Data string `json:"data"` // Punycode 编码后的记录值 + DisplayData string `json:"displayData"` // Punycode 编码前的记录值 + TTL int `json:"ttl"` // TTL + Weight int `json:"weight"` // 权重 + Preference int `json:"preference"` // MX优先级 + Dominant bool `json:"domaint"` // 是否显性URL转发 + System bool `json:"system"` // 是否系统解析记录 + Disable bool `json:"disable"` // 是否暂停 +} + +// DomainListOption 获取域名列表的参数 +type DomainListOption func(url.Values) + +func DLWithGroupID(groupID string) DomainListOption { + return func(v url.Values) { + if groupID != "" { + v.Set("groupId", groupID) + } + } +} + +func DLWithState(state int) DomainListOption { + return func(v url.Values) { + v.Set("state", strconv.Itoa(state)) + } +} + +func DLWithProductCode(productCode string) DomainListOption { + return func(v url.Values) { + if productCode != "" { + v.Set("productCode", productCode) + } + } +} + +func DLWithQuoteDomainID(quoteDomainID string) DomainListOption { + return func(v url.Values) { + if quoteDomainID != "" { + v.Set("quoteDomainId", quoteDomainID) + } + } +} + +func DLWithExpiredAtRange(begin, end int64) DomainListOption { + return func(v url.Values) { + if begin != 0 { + v.Set("expiredAtBegin", strconv.FormatInt(begin, 10)) + } + if end != 0 { + v.Set("expiredAtEnd", strconv.FormatInt(end, 10)) + } + } +} + +// RecordListOption 获取解析记录列表的参数 +type RecordListOption func(url.Values) + +func RLWithRecordType(recordType int) RecordListOption { + return func(v url.Values) { + v.Set("type", strconv.Itoa(recordType)) + } +} + +func RLWithGroupID(groupID string) RecordListOption { + return func(v url.Values) { + if groupID != "" { + v.Set("groupId", groupID) + } + } +} + +func RLWithLineID(lineID string) RecordListOption { + return func(v url.Values) { + if lineID != "" { + v.Set("lineId", lineID) + } + } +} + +func RLWithHost(host string) RecordListOption { + return func(v url.Values) { + if host != "" { + v.Set("host", host) + } + } +} + +func RLWithData(data string) RecordListOption { + return func(v url.Values) { + if data != "" { + v.Set("data", data) + } + } +} + +func RLWithDisable(disable bool) RecordListOption { + return func(v url.Values) { + v.Set("disable", strconv.FormatBool(disable)) + } +} + +func RLWithSystem(system bool) RecordListOption { + return func(v url.Values) { + v.Set("system", strconv.FormatBool(system)) + } +} + +func RLWithDominant(dominant bool) RecordListOption { + return func(v url.Values) { + v.Set("dominant", strconv.FormatBool(dominant)) + } +} diff --git a/dnslib/dnsla/public.go b/dnslib/dnsla/public.go new file mode 100644 index 0000000..d5ae0e3 --- /dev/null +++ b/dnslib/dnsla/public.go @@ -0,0 +1,20 @@ +package dnsla + +type PageOption struct { + PageIndex int `json:"pageIndex"` + PageSize int `json:"pageSize"` +} + +// NewPageOption 创建一个分页参数 +func NewPageOption(pageIndex, pageSize int) PageOption { + if !(pageSize > 0 && pageSize <= 1000) || pageIndex < 0 || pageSize <= 0 { + return PageOption{ + PageIndex: 1, + PageSize: 1000, + } + } + return PageOption{ + PageIndex: pageIndex, + PageSize: pageSize, + } +} diff --git a/dnslib/dnsla/record.go b/dnslib/dnsla/record.go new file mode 100644 index 0000000..025c8b9 --- /dev/null +++ b/dnslib/dnsla/record.go @@ -0,0 +1,36 @@ +package dnsla + +import ( + "fmt" + "net/url" + "strconv" +) + +// RecordService 解析记录服务 +type RecordService struct{ *Client } + +// ListRecords 获取域名解析记录列表 +func (r *RecordService) List(page PageOption, domainID string, options ...RecordListOption) (*RecordListResponse, error) { + params := url.Values{} + params.Set("pageIndex", strconv.Itoa(page.PageIndex)) + params.Set("pageSize", strconv.Itoa(page.PageSize)) + params.Set("domainId", domainID) + for _, option := range options { + option(params) + } + resp, err := r.client.R(). + SetQueryParamsFromValues(params). + SetResult(&RecordListResponse{}). + Get("/api/recordList") + if err != nil { + return nil, err + } + if resp.IsError() { + return nil, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode(), resp.String()) + } + result, ok := resp.Result().(*RecordListResponse) + if !ok { + return nil, fmt.Errorf("failed to cast response to *RecordResponse") + } + return result, nil +} diff --git a/go.mod b/go.mod index 6ab2beb..bba9ace 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/allegro/bigcache/v3 v3.1.0 github.com/alyx/go-daddy v0.0.0-20240819232932-c2e4d209da9b github.com/charmbracelet/log v0.2.2 + github.com/go-resty/resty/v2 v2.14.0 github.com/golang-module/carbon/v2 v2.3.12 github.com/prometheus/client_golang v1.16.0 github.com/robfig/cron/v3 v3.0.1 @@ -30,7 +31,6 @@ require ( github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.3.1 // indirect github.com/clbanning/mxj/v2 v2.5.5 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21 // indirect diff --git a/go.sum b/go.sum index 6c18ba6..8436998 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU= +github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg= github.com/golang-module/carbon/v2 v2.3.12 h1:VC1DwN1kBwJkh5MjXmTFryjs5g4CWyoM8HAHffZPX/k= github.com/golang-module/carbon/v2 v2.3.12/go.mod h1:HNsedGzXGuNciZImYP2OMnpiwq/vhIstR/vn45ib5cI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -153,10 +155,17 @@ golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -168,7 +177,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -177,6 +190,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -189,15 +205,24 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -205,13 +230,20 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/provider/dnsla.go b/pkg/provider/dnsla.go index 662d90f..875aed1 100644 --- a/pkg/provider/dnsla.go +++ b/pkg/provider/dnsla.go @@ -1,3 +1,204 @@ package provider -// TODO: 待实现 +import ( + "encoding/json" + "fmt" + "strconv" + "sync" + "time" + + "github.com/eryajf/cloud_dns_exporter/dnslib/dnsla" + "github.com/eryajf/cloud_dns_exporter/public" + "github.com/eryajf/cloud_dns_exporter/public/logger" + "github.com/golang-module/carbon/v2" +) + +type DNSLaDNS struct { + account public.Account + client *dnsla.Client +} + +// NewDNSLaClient 初始化客户端 +func NewDNSLaClient(secretID, secretKey string) (*dnsla.Client, error) { + client, err := dnsla.NewClient(secretID, secretKey) + if err != nil { + return nil, err + } + return client, nil +} + +// NewDNSLaDNS 创建 DNSLaDNS 实例 +func NewDNSLaDNS(account public.Account) (*DNSLaDNS, error) { + client, err := NewDNSLaClient(account.SecretID, account.SecretKey) + if err != nil { + return nil, err + } + return &DNSLaDNS{ + account: account, + client: client, + }, nil +} + +// ListDomains 获取域名列表 +func (d *DNSLaDNS) ListDomains() ([]Domain, error) { + gd, err := NewDNSLaDNS(public.Account{ + CloudProvider: d.account.CloudProvider, + CloudName: d.account.CloudName, + SecretID: d.account.SecretID, + SecretKey: d.account.SecretKey, + }) + if err != nil { + return nil, err + } + d.client = gd.client + var dataObj []Domain + domains, err := d.getDomainList() + if err != nil { + return nil, err + } + for _, v := range domains { + dataObj = append(dataObj, Domain{ + CloudProvider: d.account.CloudProvider, + CloudName: d.account.CloudName, + DomainID: v.ID, + DomainName: v.Domain, + DomainRemark: v.Domain, + DomainStatus: oneStatus(strconv.Itoa(v.State)), + CreatedDate: carbon.CreateFromTimestampMilli(v.CreatedAt).ToDateTimeString(), + ExpiryDate: carbon.CreateFromTimestampMilli(v.ExpiredAt).ToDateTimeString(), + DaysUntilExpiry: carbon.Now().DiffInDays(carbon.Parse(carbon.CreateFromTimestampMilli(v.ExpiredAt).ToDateTimeString())), + }) + } + return dataObj, nil +} + +// ListRecords 获取记录列表 +func (d *DNSLaDNS) ListRecords() ([]Record, error) { + var ( + dataObj []Record + domains []Domain + wg sync.WaitGroup + mu sync.Mutex + ) + tcd, err := NewDNSLaDNS(public.Account{ + CloudProvider: d.account.CloudProvider, + CloudName: d.account.CloudName, + SecretID: d.account.SecretID, + SecretKey: d.account.SecretKey, + }) + if err != nil { + return nil, err + } + d.client = tcd.client + rst, err := public.Cache.Get(public.DomainList + "_" + d.account.CloudProvider + "_" + d.account.CloudName) + if err != nil { + return nil, err + } + err = json.Unmarshal(rst, &domains) + if err != nil { + return nil, err + } + results := make(map[string][]dnsla.Record) + ticker := time.NewTicker(time.Second) + for _, domain := range domains { + wg.Add(1) + go func(domainName, domainId string) { + defer wg.Done() + <-ticker.C + records, err := d.getRecordList(domainId) + if err != nil { + logger.Error(fmt.Sprintf("[ %s_%s ] get record list failed: %v", d.account.CloudProvider, d.account.CloudName, err)) + } + if len(records) == 0 { + return + } + mu.Lock() + results[domainName] = records + mu.Unlock() + }(domain.DomainName, domain.DomainID) + } + wg.Wait() + for domain, records := range results { + for _, v := range records { + dataObj = append(dataObj, Record{ + CloudProvider: d.account.CloudProvider, + CloudName: d.account.CloudName, + DomainName: domain, + RecordID: v.Data, + RecordType: getRecordType(v.Type), + RecordName: v.DisplayHost, + RecordValue: v.Data, + RecordTTL: strconv.Itoa(v.TTL), + RecordWeight: strconv.Itoa(v.Weight), + RecordStatus: "enable", + RecordRemark: v.DisplayHost, + FullRecord: v.DisplayHost + "." + domain, + }) + } + } + return dataObj, nil +} + +// https://www.dns.la/docs/ApiDoc +// GetDomainList 获取云解析中域名列表 +func (d *DNSLaDNS) getDomainList() ([]dnsla.Domain, error) { + domains, err := d.client.Domains.List(dnsla.NewPageOption(1, 500)) + if err != nil { + return nil, err + } + + return domains.Data.Results, nil +} + +// https://www.dns.la/docs/ApiDoc +// RecordList 域名记录列表 +func (d *DNSLaDNS) getRecordList(domain string) ([]dnsla.Record, error) { + // TODO 目前写死的获取1000条记录 + rds, err := d.client.Records.List(dnsla.NewPageOption(1, 1000), domain) + if err != nil { + fmt.Printf("Error listing records: %v\n", err) + } + return rds.Data.Results, err +} + +// RecordType 表示DNS记录类型 +type RecordType int + +// 定义常量 +const ( + A RecordType = 1 + NS RecordType = 2 + CNAME RecordType = 5 + MX RecordType = 15 + TXT RecordType = 16 + AAAA RecordType = 28 + SRV RecordType = 33 + CAA RecordType = 257 + URLForward RecordType = 256 +) + +// getRecordType 将数字类型转换为对应的记录类型字符串 +func getRecordType(typeNum int) string { + switch RecordType(typeNum) { + case A: + return "A" + case NS: + return "NS" + case CNAME: + return "CNAME" + case MX: + return "MX" + case TXT: + return "TXT" + case AAAA: + return "AAAA" + case SRV: + return "SRV" + case CAA: + return "CAA" + case URLForward: + return "URL转发" + default: + return "Unknown" + } +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index bc5a05f..48b15b9 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -42,6 +42,16 @@ func init() { }, } }) + Factory.Register(public.DNSLaDnsProvider, func(account map[string]string) DNSProvider { + return &DNSLaDNS{ + account: public.Account{ + CloudProvider: public.DNSLaDnsProvider, + CloudName: account["name"], + SecretID: account["secretId"], + SecretKey: account["secretKey"], + }, + } + }) } // Doamin 域名信息 @@ -133,7 +143,7 @@ func (f *DNSProviderFactory) Create(cloudProvider string, account map[string]str // 统一记录状态的值 func oneStatus(status string) string { // tencent 的记录状态是 ENABLE 和 DISABLE - if status == "ENABLE" || status == "ACTIVE" { + if status == "ENABLE" || status == "ACTIVE" || status == "1" { return "enable" } if status == "DISABLE" { diff --git a/public/public.go b/public/public.go index 19e29cb..d34abbc 100644 --- a/public/public.go +++ b/public/public.go @@ -23,6 +23,7 @@ const ( TencentDnsProvider string = "tencent" AliyunDnsProvider string = "aliyun" GodaddyDnsProvider string = "godaddy" + DNSLaDnsProvider string = "dnsla" HuaweiDnsProvider string = "huawei" // Metrics Name DomainList string = "domain_list"