diff --git a/config.example.yaml b/config.example.yaml index 2f31e2e..438fea2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -30,4 +30,9 @@ cloud_providers: - name: d1 secretId: "xxxxx" secretKey: "xxxxx" - # 目前支持Tencent, Aliyun, Godaddy,DNALA, Amazon,如需支持更多云厂商,请提交 issue,也欢迎 PR + cloudflare: + accounts: + - name: a1 + secretId: "xxxxx" # 注册邮箱 + secretKey: "xxxxx" # ApiKey密钥 + # 目前支持Tencent, Aliyun, Godaddy, DNALA, Amazon, CloudFlare,如需支持更多云厂商,请提交 issue,也欢迎 PR diff --git a/go.mod b/go.mod index 88b6da0..844283c 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 github.com/aws/aws-sdk-go-v2/service/route53domains v1.25.6 github.com/charmbracelet/log v0.2.2 + github.com/cloudflare/cloudflare-go v0.103.0 github.com/go-resty/resty/v2 v2.14.0 github.com/golang-module/carbon/v2 v2.3.12 github.com/google/uuid v1.6.0 @@ -39,12 +40,16 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/smithy-go v1.20.4 // indirect github.com/clbanning/mxj/v2 v2.5.5 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tjfoc/gmsm v1.3.2 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/pkg/provider/cloudflare.go b/pkg/provider/cloudflare.go index 662d90f..a231654 100644 --- a/pkg/provider/cloudflare.go +++ b/pkg/provider/cloudflare.go @@ -1,3 +1,212 @@ package provider -// TODO: 待实现 +import ( + "context" + "encoding/json" + "fmt" + "github.com/alibabacloud-go/tea/tea" + "github.com/cloudflare/cloudflare-go" + "github.com/eryajf/cloud_dns_exporter/public" + "github.com/golang-module/carbon/v2" + "sync" + "time" +) + +type CloudFlareDNS struct { + account public.Account + client *cloudflare.API +} + +type Header struct { + XAuthEmail interface{} `json:"X-Auth-Email"` + XAuthKey interface{} `json:"X-Auth-Key"` + ContentType string `json:"Content-Type"` +} + +func NewCloudflareDNSClient(token string, email string) (*cloudflare.API, error) { + return cloudflare.New(token, email) +} + +func NewCloudFlareDNS(account public.Account) *CloudFlareDNS { + client, _ := NewCloudflareDNSClient(account.SecretKey, account.SecretID) + return &CloudFlareDNS{ + account: account, + client: client, + } +} + +func (cf *CloudFlareDNS) ListDomains() ([]Domain, error) { + cfd := NewCloudFlareDNS(public.Account{ + CloudProvider: cf.account.CloudProvider, + CloudName: cf.account.CloudName, + SecretID: cf.account.SecretID, + SecretKey: cf.account.SecretKey, + }) + cf.client = cfd.client + var ( + dataObj []Domain + wg sync.WaitGroup + mu sync.Mutex + ) + domains, err := cf.getDomainList() + if err != nil { + return nil, err + } + ticker := time.NewTicker(100 * time.Millisecond) + for _, domain := range domains { + wg.Add(1) + go func(domain cloudflare.Zone) { + defer wg.Done() + <-ticker.C + domainCreateAndExpiryDate, _ := cf.getDomainCreateAndExpiryDate(domain) + mu.Lock() + dataObj = append(dataObj, Domain{ + CloudName: domain.Name, + CloudProvider: cf.account.CloudProvider, + CreatedDate: domainCreateAndExpiryDate.CreatedDate, + DaysUntilExpiry: domainCreateAndExpiryDate.DaysUntilExpiry, + DomainID: domain.ID, + DomainName: domain.Name, + DomainRemark: tea.StringValue(nil), + DomainStatus: domain.Status, + ExpiryDate: domainCreateAndExpiryDate.ExpiryDate, + }) + mu.Unlock() + }(domain) + } + wg.Wait() + return dataObj, err +} + +func (cf *CloudFlareDNS) ListRecords() ([]Record, error) { + var ( + dataObj []Record + domains []Domain + wg sync.WaitGroup + mu sync.Mutex + ) + cfd := NewCloudFlareDNS(public.Account{ + CloudProvider: cf.account.CloudProvider, + CloudName: cf.account.CloudName, + SecretID: cf.account.SecretID, + SecretKey: cf.account.SecretKey, + }) + cf.client = cfd.client + rst, err := public.Cache.Get(public.DomainList + "_" + cf.account.CloudProvider + "_" + cf.account.CloudName) + if err != nil { + return nil, err + } + err = json.Unmarshal(rst, &domains) + if err != nil { + return nil, err + } + results := make(map[string][]cloudflare.DNSRecord) + ticker := time.NewTicker(100 * time.Millisecond) + for _, domain := range domains { + wg.Add(1) + go func(domain Domain) { + defer wg.Done() + <-ticker.C + records, err := cf.getRecordList(domain.DomainName) + if err != nil { + fmt.Printf("cloudflare get record list error: %v", err) + return + } + mu.Lock() + results[domain.DomainName] = records + mu.Unlock() + }(domain) + } + wg.Wait() + for domain, records := range results { + for _, record := range records { + dataObj = append(dataObj, Record{ + CloudName: cf.account.CloudName, + CloudProvider: cf.account.CloudProvider, + DomainName: domain, + RecordID: record.ID, + RecordName: record.Name, + RecordType: record.Type, + RecordRemark: tea.StringValue(nil), + RecordStatus: "enable", + RecordTTL: fmt.Sprintf("%d", record.TTL), + FullRecord: record.Name, + }) + } + } + return dataObj, err +} + +// getDomainList 获取解析域域名列表 +func (cf *CloudFlareDNS) getDomainList() (rst []cloudflare.Zone, err error) { + client, err := NewCloudflareDNSClient(cf.account.SecretKey, cf.account.SecretID) + if err != nil { + fmt.Printf("cloudflare client init error: %v", err) + return + } + zones, err := client.ListZones(context.Background()) + if err != nil { + fmt.Printf("cloudflare list zones error: %v", err) + return + } + for _, zone := range zones { + rst = append(rst, zone) + } + return +} + +func (cf *CloudFlareDNS) getAccountId() (account cloudflare.Account, err error) { + client, _ := NewCloudflareDNSClient(cf.account.SecretKey, cf.account.SecretID) + accounts, _, err := client.Accounts(context.Background(), cloudflare.AccountsListParams{}) + if err != nil { + return + } + for _, a := range accounts { + account = a + } + return +} + +func (cf *CloudFlareDNS) getRecordList(domain string) (rst []cloudflare.DNSRecord, err error) { + page := 1 + pageSize := 2 + client, _ := NewCloudflareDNSClient(cf.account.SecretKey, cf.account.SecretID) + zoneID, err := client.ZoneIDByName(domain) + if err != nil { + return + } + for { + records, r, err := client.ListDNSRecords(context.Background(), cloudflare.ZoneIdentifier(zoneID), cloudflare.ListDNSRecordsParams{ + ResultInfo: cloudflare.ResultInfo{Page: page, PerPage: pageSize}, + }) + if err != nil { + return nil, err + } + for _, record := range records { + rst = append(rst, record) + } + if page*pageSize > r.Total { + break + } + page++ + } + return +} + +func (cf *CloudFlareDNS) getDomainCreateAndExpiryDate(domain cloudflare.Zone) (d Domain, err error) { + client, err := NewCloudflareDNSClient(cf.account.SecretKey, cf.account.SecretID) + if err != nil { + return + } + account, err := cf.getAccountId() + if err != nil { + return + } + domainInfo, err := client.RegistrarDomain(context.Background(), account.ID, domain.Name) + d.CreatedDate = domainInfo.CreatedAt.String() + d.ExpiryDate = domainInfo.ExpiresAt.String() + if d.ExpiryDate != "" { + d.DaysUntilExpiry = carbon.Now().DiffInDays(carbon.Parse(d.ExpiryDate)) + } + return +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 67a7bad..5ff8912 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -62,6 +62,16 @@ func init() { }, } }) + Factory.Register(public.CloudFlareDnsProvider, func(account map[string]string) DNSProvider { + return &CloudFlareDNS{ + account: public.Account{ + CloudProvider: public.CloudFlareDnsProvider, + CloudName: account["name"], + SecretID: account["secretId"], + SecretKey: account["secretKey"], + }, + } + }) } // Doamin 域名信息 diff --git a/public/public.go b/public/public.go index 651976a..cd8c484 100644 --- a/public/public.go +++ b/public/public.go @@ -22,12 +22,13 @@ func InitSvc() { const ( // Cloud Providers - TencentDnsProvider string = "tencent" - AliyunDnsProvider string = "aliyun" - GodaddyDnsProvider string = "godaddy" - DNSLaDnsProvider string = "dnsla" - HuaweiDnsProvider string = "huawei" - AmazonDnsProvider string = "amazon" + TencentDnsProvider string = "tencent" + AliyunDnsProvider string = "aliyun" + GodaddyDnsProvider string = "godaddy" + DNSLaDnsProvider string = "dnsla" + HuaweiDnsProvider string = "huawei" + AmazonDnsProvider string = "amazon" + CloudFlareDnsProvider string = "cloudflare" // Metrics Name DomainList string = "domain_list" RecordList string = "record_list"