add the provider for the tencent cloud.

Signed-off-by: misakazhou <misakazhou@tencent.com>
This commit is contained in:
misakazhou 2022-02-28 16:02:41 +08:00
parent e47d56112a
commit a2e7ffc36a
17 changed files with 2119 additions and 1 deletions

View File

@ -57,6 +57,8 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
* [Gandi](https://www.gandi.net)
* [ANS Group SafeDNS](https://portal.ans.co.uk/safedns/)
* [IBM Cloud DNS](https://www.ibm.com/cloud/dns)
* [TencentCloud PrivateDNS](https://cloud.tencent.com/product/privatedns)
* [TencentCloud DNSPod](https://cloud.tencent.com/product/cns)
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
@ -115,6 +117,7 @@ The following table clarifies the current status of the providers according to t
| Gandi | Alpha | @packi |
| SafeDNS | Alpha | @assureddt |
| IBMCloud | Alpha | @hughhuangzh |
| TencentCloud | Alpha | @Hyzhou |
## Kubernetes version compatibility
@ -183,6 +186,7 @@ The following tutorials are provided:
* [SafeDNS](docs/tutorials/UKFast_SafeDNS.md)
* [IBM Cloud](docs/tutorials/ibmcloud.md)
* [Nodes as source](docs/tutorials/nodes.md)
* [TencentCloud](docs/tutorials/tencentcloud.md)
### Running Locally

View File

@ -0,0 +1,208 @@
# Setting up ExternalDNS for Tencent Cloud
## External Dns Version
* Make sure to use **>=1.7.2** version of ExternalDNS for this tutorial
## Set up PrivateDns or DNSPod
Tencent Cloud DNSPod Service is the domain name resolution and management service for public access.
Tencent Cloud PrivateDNS Service is the domain name resolution and management service for VPC internal access.
* If you want to use internal dns service in Tencent Cloud.
1. Set up the args `--tencent-cloud-zone-type=private`
2. Create a DNS domain in PrivateDNS console. DNS domain which will contain the managed DNS records.
* If you want to use public dns service in Tencent Cloud.
1. Set up the args `--tencent-cloud-zone-type=public`
2. Create a Domain in DnsPod console. DNS domain which will contain the managed DNS records.
## Set up CAM for API Key
In Tencent CAM Console. you may get the secretId and secretKey pair. make sure the key pair has those Policy.
```json
{
"version": "2.0",
"statement": [
{
"effect": "allow",
"action": [
"dnspod:ModifyRecord",
"dnspod:DeleteRecord",
"dnspod:CreateRecord",
"dnspod:DescribeRecordList",
"dnspod:DescribeDomainList"
],
"resource": [
"*"
]
},
{
"effect": "allow",
"action": [
"privatedns:DescribePrivateZoneList",
"privatedns:DescribePrivateZoneRecordList",
"privatedns:CreatePrivateZoneRecord",
"privatedns:DeletePrivateZoneRecord",
"privatedns:ModifyPrivateZoneRecord"
],
"resource": [
"*"
]
}
]
}
```
# Deploy ExternalDNS
## Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: v1
kind: ConfigMap
metadata:
name: external-dns
data:
tencent-cloud.json: |
{
"regionId": "ap-shanghai",
"secretId": "******",
"secretKey": "******",
"vpcId": "vpc-******"
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- args:
- --source=service
- --source=ingress
- --domain-filter=external-dns-test.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=tencentcloud
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records
- --tencent-cloud-zone-type=private # only look at private hosted zones. set `public` to use the public dns service.
- --tencent-cloud-config-file=/etc/kubernetes/tencent-cloud.json
image: k8s.gcr.io/external-dns/external-dns:v1.7.2
imagePullPolicy: Always
name: external-dns
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/kubernetes
name: config-volume
readOnly: true
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: external-dns
serviceAccountName: external-dns
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
items:
- key: tencent-cloud.json
path: tencent-cloud.json
name: external-dns
name: config-volume
```
# Example
## Service
```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.com
external-dns.alpha.kubernetes.io/internal-hostname: nginx-internal.external-dns-test.com
external-dns.alpha.kubernetes.io/ttl: "600"
spec:
type: LoadBalancer
ports:
- port: 80
name: http
targetPort: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
```
`nginx.external-dns-test.com` will record to the Loadbalancer VIP.
`nginx-internal.external-dns-test.com` will record to the ClusterIP.
all of the DNS Record ttl will be 600.
# Attention
This makes ExternalDNS safe for running in environments where there are other records managed via other means.

3
go.mod
View File

@ -142,6 +142,9 @@ require (
github.com/smartystreets/gunit v1.3.4 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.344
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.344
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.344
github.com/terra-farm/udnssdk v1.3.5 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect
go.mongodb.org/mongo-driver v1.5.1 // indirect

6
go.sum
View File

@ -1349,6 +1349,12 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.344 h1:QhPDamT0YL04UaoteA9AEHnE/sklwYr+VSKd/pPQ6r8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.344/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.344 h1:pdwJ6T3iEjP5nB9Mgi4y/OBO8XNtkGN2/+mjGZ8yCbw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.344/go.mod h1:CuOaLxOQr477GhMWAQPYQFUJrsZbW+ZqkAgP2uHDZXg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.344 h1:q4r39zJkMyHvrORok48IOJz/nJ235dIkHStA9LZYwgw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.344/go.mod h1:En+pdagcHkAASorHT1l8R6tUtieRNNxaQ7nfyqWPefk=
github.com/terra-farm/udnssdk v1.3.5 h1:MNR3adfuuEK/l04+jzo8WW/0fnorY+nW515qb3vEr6I=
github.com/terra-farm/udnssdk v1.3.5/go.mod h1:8RnM56yZTR7mYyUIvrDgXzdRaEyFIzqdEi7+um26Sv8=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=

View File

@ -64,6 +64,7 @@ import (
"sigs.k8s.io/external-dns/provider/rfc2136"
"sigs.k8s.io/external-dns/provider/safedns"
"sigs.k8s.io/external-dns/provider/scaleway"
"sigs.k8s.io/external-dns/provider/tencentcloud"
"sigs.k8s.io/external-dns/provider/transip"
"sigs.k8s.io/external-dns/provider/ultradns"
"sigs.k8s.io/external-dns/provider/vinyldns"
@ -334,6 +335,8 @@ func main() {
p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun)
case "safedns":
p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun)
case "tencentcloud":
p, err = tencentcloud.NewTencentCloudProvider(domainFilter, zoneIDFilter, cfg.TencentCloudConfigFile, cfg.TencentCloudZoneType, cfg.DryRun)
default:
log.Fatalf("unknown dns provider: %s", cfg.Provider)
}

View File

@ -192,6 +192,8 @@ type Config struct {
OCPRouterName string
IBMCloudProxied bool
IBMCloudConfigFile string
TencentCloudConfigFile string
TencentCloudZoneType string
}
var defaultConfig = &Config{
@ -223,6 +225,7 @@ var defaultConfig = &Config{
GoogleBatchChangeInterval: time.Second,
GoogleZoneVisibility: "",
DomainFilter: []string{},
ZoneIDFilter: []string{},
ExcludeDomains: []string{},
RegexDomainFilter: regexp.MustCompile(""),
RegexDomainExclusion: regexp.MustCompile(""),
@ -326,6 +329,8 @@ var defaultConfig = &Config{
GoDaddyOTE: false,
IBMCloudProxied: false,
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
TencentCloudZoneType: "",
}
// NewConfig returns new Config object
@ -415,7 +420,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns, tencentcloud)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "tencentcloud")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)
@ -443,6 +448,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (required when --provider=azure-private-dns)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
app.Flag("azure-subscription-id", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure-private-dns)").Default(defaultConfig.AzureSubscriptionID).StringVar(&cfg.AzureSubscriptionID)
app.Flag("azure-user-assigned-identity-client-id", "When using the Azure provider, override the client id of user assigned identity in config file (optional)").Default("").StringVar(&cfg.AzureUserAssignedIdentityClientID)
app.Flag("tencent-cloud-config-file", "When using the Tencent Cloud provider, specify the Tencent Cloud configuration file (required when --provider=tencentcloud").Default(defaultConfig.TencentCloudConfigFile).StringVar(&cfg.TencentCloudConfigFile)
app.Flag("tencent-cloud-zone-type", "When using the Tencent Cloud provider, filter for zones with visibility (optional, options: public, private)").Default(defaultConfig.TencentCloudZoneType).EnumVar(&cfg.TencentCloudZoneType, "", "public", "private")
// Flags related to BlueCat provider
app.Flag("bluecat-dns-configuration", "When using the Bluecat provider, specify the Bluecat DNS configuration string (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatDNSConfiguration)

View File

@ -127,6 +127,8 @@ var (
OCPRouterName: "default",
IBMCloudProxied: false,
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
TencentCloudZoneType: "",
}
overriddenConfig = &Config{
@ -235,6 +237,8 @@ var (
RFC2136BatchChangeSize: 100,
IBMCloudProxied: true,
IBMCloudConfigFile: "ibmcloud.json",
TencentCloudConfigFile: "tencent-cloud.json",
TencentCloudZoneType: "private",
}
)
@ -373,6 +377,8 @@ func TestParseFlags(t *testing.T) {
"--rfc2136-batch-change-size=100",
"--ibmcloud-proxied",
"--ibmcloud-config-file=ibmcloud.json",
"--tencent-cloud-config-file=tencent-cloud.json",
"--tencent-cloud-zone-type=private",
},
envVars: map[string]string{},
expected: overriddenConfig,
@ -486,6 +492,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_RFC2136_BATCH_CHANGE_SIZE": "100",
"EXTERNAL_DNS_IBMCLOUD_PROXIED": "1",
"EXTERNAL_DNS_IBMCLOUD_CONFIG_FILE": "ibmcloud.json",
"EXTERNAL_DNS_TENCENT_CLOUD_CONFIG_FILE": "tencent-cloud.json",
"EXTERNAL_DNS_TENCENT_CLOUD_ZONE_TYPE": "private",
},
expected: overriddenConfig,
},

View File

@ -0,0 +1,60 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudapi
import (
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
)
type Action struct {
Service string `json:"service"`
Name string `json:"name"`
ReadOnly bool `json:"readOnly"`
}
var (
/* PrivateDNS */
CreatePrivateZoneRecord = Action{Service: "PrivateDns", Name: "CreatePrivateZoneRecord", ReadOnly: false}
DeletePrivateZoneRecord = Action{Service: "PrivateDns", Name: "DeletePrivateZoneRecord", ReadOnly: false}
ModifyPrivateZoneRecord = Action{Service: "PrivateDns", Name: "ModifyPrivateZoneRecord", ReadOnly: false}
DescribePrivateZoneList = Action{Service: "PrivateDns", Name: "DescribePrivateZoneList", ReadOnly: true}
DescribePrivateZoneRecordList = Action{Service: "PrivateDns", Name: "DescribePrivateZoneRecordList", ReadOnly: true}
/* DNSPod */
DescribeDomainList = Action{Service: "DnsPod", Name: "DescribeDomainList", ReadOnly: true}
DescribeRecordList = Action{Service: "DnsPod", Name: "DescribeRecordList", ReadOnly: true}
CreateRecord = Action{Service: "DnsPod", Name: "CreateRecord", ReadOnly: false}
DeleteRecord = Action{Service: "DnsPod", Name: "DeleteRecord", ReadOnly: false}
ModifyRecord = Action{Service: "DnsPod", Name: "ModifyRecord", ReadOnly: false}
)
type TencentAPIService interface {
// PrivateDNS
CreatePrivateZoneRecord(request *privatedns.CreatePrivateZoneRecordRequest) (response *privatedns.CreatePrivateZoneRecordResponse, err error)
DeletePrivateZoneRecord(request *privatedns.DeletePrivateZoneRecordRequest) (response *privatedns.DeletePrivateZoneRecordResponse, err error)
ModifyPrivateZoneRecord(request *privatedns.ModifyPrivateZoneRecordRequest) (response *privatedns.ModifyPrivateZoneRecordResponse, err error)
DescribePrivateZoneList(request *privatedns.DescribePrivateZoneListRequest) (response *privatedns.DescribePrivateZoneListResponse, err error)
DescribePrivateZoneRecordList(request *privatedns.DescribePrivateZoneRecordListRequest) (response *privatedns.DescribePrivateZoneRecordListResponse, err error)
// DNSPod
DescribeDomainList(request *dnspod.DescribeDomainListRequest) (response *dnspod.DescribeDomainListResponse, err error)
DescribeRecordList(request *dnspod.DescribeRecordListRequest) (response *dnspod.DescribeRecordListResponse, err error)
CreateRecord(request *dnspod.CreateRecordRequest) (response *dnspod.CreateRecordResponse, err error)
DeleteRecord(request *dnspod.DeleteRecordRequest) (response *dnspod.DeleteRecordResponse, err error)
ModifyRecord(request *dnspod.ModifyRecordRequest) (response *dnspod.ModifyRecordResponse, err error)
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudapi
import (
"fmt"
"sync"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
"go.uber.org/ratelimit"
)
type TencentClientSetService interface {
PrivateDnsCli(action string) *privatedns.Client
DnsPodCli(action string) *dnspod.Client
}
func NewTencentClientSetService(region string, rate int, secretId string, secretKey string, internetEndpoint bool) *defaultTencentClientSetService {
p := &defaultTencentClientSetService{
Region: region,
RateLimit: rate,
}
cred := common.NewCredential(secretId, secretKey)
privatednsProf := profile.NewClientProfile()
if !internetEndpoint {
privatednsProf.HttpProfile.Endpoint = "privatedns.internal.tencentcloudapi.com"
}
p.privateDnsClient, _ = privatedns.NewClient(cred, region, privatednsProf)
dnsPodProf := profile.NewClientProfile()
if !internetEndpoint {
dnsPodProf.HttpProfile.Endpoint = "dnspod.internal.tencentcloudapi.com"
}
p.dnsPodClient, _ = dnspod.NewClient(cred, region, dnsPodProf)
return p
}
type defaultTencentClientSetService struct {
Region string
RateLimit int
RateLimitSyncMap sync.Map
privateDnsClient *privatedns.Client
dnsPodClient *dnspod.Client
}
func (p *defaultTencentClientSetService) checkRateLimit(request, method string) {
action := fmt.Sprintf("%s_%s", request, method)
if rl, ok := p.RateLimitSyncMap.LoadOrStore(action, ratelimit.New(p.RateLimit, ratelimit.WithoutSlack)); ok {
rl.(ratelimit.Limiter).Take()
}
}
func (p *defaultTencentClientSetService) PrivateDnsCli(action string) *privatedns.Client {
p.checkRateLimit("privateDns", action)
return p.privateDnsClient
}
func (p *defaultTencentClientSetService) DnsPodCli(action string) *dnspod.Client {
p.checkRateLimit("dnsPod", action)
return p.dnsPodClient
}

View File

@ -0,0 +1,247 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudapi
import (
"math/rand"
"time"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
)
type mockAPIService struct {
privateZones []*privatedns.PrivateZone
privateZoneRecords map[string][]*privatedns.PrivateZoneRecord
dnspodDomains []*dnspod.DomainListItem
dnspodRecords map[string][]*dnspod.RecordListItem
}
func NewMockService(privateZones []*privatedns.PrivateZone, privateZoneRecords map[string][]*privatedns.PrivateZoneRecord, dnspodDomains []*dnspod.DomainListItem, dnspodRecords map[string][]*dnspod.RecordListItem) *mockAPIService {
rand.Seed(time.Now().Unix())
return &mockAPIService{
privateZones: privateZones,
privateZoneRecords: privateZoneRecords,
dnspodDomains: dnspodDomains,
dnspodRecords: dnspodRecords,
}
}
////////////////////////////////////////////////////////////////
// PrivateDns API
////////////////////////////////////////////////////////////////
func (api *mockAPIService) CreatePrivateZoneRecord(request *privatedns.CreatePrivateZoneRecordRequest) (response *privatedns.CreatePrivateZoneRecordResponse, err error) {
randomRecordId := RandStringRunes(8)
if _, exist := api.privateZoneRecords[*request.ZoneId]; !exist {
api.privateZoneRecords[*request.ZoneId] = make([]*privatedns.PrivateZoneRecord, 0)
}
if request.TTL == nil {
request.TTL = common.Int64Ptr(300)
}
api.privateZoneRecords[*request.ZoneId] = append(api.privateZoneRecords[*request.ZoneId], &privatedns.PrivateZoneRecord{
RecordId: common.StringPtr(randomRecordId),
ZoneId: request.ZoneId,
SubDomain: request.SubDomain,
RecordType: request.RecordType,
RecordValue: request.RecordValue,
TTL: request.TTL,
})
return response, nil
}
func (api *mockAPIService) DeletePrivateZoneRecord(request *privatedns.DeletePrivateZoneRecordRequest) (response *privatedns.DeletePrivateZoneRecordResponse, err error) {
result := make([]*privatedns.PrivateZoneRecord, 0)
if _, exist := api.privateZoneRecords[*request.ZoneId]; !exist {
return response, nil
}
for _, privateZoneRecord := range api.privateZoneRecords[*request.ZoneId] {
deleteflag := false
if request.RecordIdSet != nil && len(request.RecordIdSet) != 0 {
for _, recordId := range request.RecordIdSet {
if *privateZoneRecord.RecordId == *recordId {
deleteflag = true
break
}
}
}
if request.RecordId != nil && *request.RecordId == *privateZoneRecord.RecordId {
deleteflag = true
}
if !deleteflag {
result = append(result, privateZoneRecord)
}
}
api.privateZoneRecords[*request.ZoneId] = result
return response, nil
}
func (api *mockAPIService) ModifyPrivateZoneRecord(request *privatedns.ModifyPrivateZoneRecordRequest) (response *privatedns.ModifyPrivateZoneRecordResponse, err error) {
if _, exist := api.privateZoneRecords[*request.ZoneId]; !exist {
return response, nil
}
for _, privateZoneRecord := range api.privateZoneRecords[*request.ZoneId] {
if *privateZoneRecord.RecordId != *request.RecordId {
continue
}
privateZoneRecord.ZoneId = request.ZoneId
privateZoneRecord.SubDomain = request.SubDomain
privateZoneRecord.RecordType = request.RecordType
privateZoneRecord.RecordValue = request.RecordValue
privateZoneRecord.TTL = request.TTL
}
return response, nil
}
func (api *mockAPIService) DescribePrivateZoneList(request *privatedns.DescribePrivateZoneListRequest) (response *privatedns.DescribePrivateZoneListResponse, err error) {
response = privatedns.NewDescribePrivateZoneListResponse()
response.Response = &struct {
TotalCount *int64 `json:"TotalCount,omitempty" name:"TotalCount"`
PrivateZoneSet []*privatedns.PrivateZone `json:"PrivateZoneSet,omitempty" name:"PrivateZoneSet"`
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
}{
TotalCount: common.Int64Ptr(int64(len(api.privateZones))),
PrivateZoneSet: api.privateZones,
}
return response, nil
}
func (api *mockAPIService) DescribePrivateZoneRecordList(request *privatedns.DescribePrivateZoneRecordListRequest) (response *privatedns.DescribePrivateZoneRecordListResponse, err error) {
response = privatedns.NewDescribePrivateZoneRecordListResponse()
response.Response = &struct {
TotalCount *int64 `json:"TotalCount,omitempty" name:"TotalCount"`
RecordSet []*privatedns.PrivateZoneRecord `json:"RecordSet,omitempty" name:"RecordSet"`
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
}{}
if _, exist := api.privateZoneRecords[*request.ZoneId]; !exist {
response.Response.TotalCount = common.Int64Ptr(0)
response.Response.RecordSet = make([]*privatedns.PrivateZoneRecord, 0)
return response, nil
}
response.Response.TotalCount = common.Int64Ptr(int64(len(api.privateZoneRecords[*request.ZoneId])))
response.Response.RecordSet = api.privateZoneRecords[*request.ZoneId]
return response, nil
}
////////////////////////////////////////////////////////////////
// DnsPod API
////////////////////////////////////////////////////////////////
func (api *mockAPIService) DescribeDomainList(request *dnspod.DescribeDomainListRequest) (response *dnspod.DescribeDomainListResponse, err error) {
response = dnspod.NewDescribeDomainListResponse()
response.Response = &struct {
DomainCountInfo *dnspod.DomainCountInfo `json:"DomainCountInfo,omitempty" name:"DomainCountInfo"`
DomainList []*dnspod.DomainListItem `json:"DomainList,omitempty" name:"DomainList"`
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
}{}
response.Response.DomainList = api.dnspodDomains
response.Response.DomainCountInfo = &dnspod.DomainCountInfo{
AllTotal: common.Uint64Ptr(uint64(len(api.dnspodDomains))),
}
return response, nil
}
func (api *mockAPIService) DescribeRecordList(request *dnspod.DescribeRecordListRequest) (response *dnspod.DescribeRecordListResponse, err error) {
response = dnspod.NewDescribeRecordListResponse()
response.Response = &struct {
RecordCountInfo *dnspod.RecordCountInfo `json:"RecordCountInfo,omitempty" name:"RecordCountInfo"`
RecordList []*dnspod.RecordListItem `json:"RecordList,omitempty" name:"RecordList"`
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
}{}
if _, exist := api.dnspodRecords[*request.Domain]; !exist {
response.Response.RecordList = make([]*dnspod.RecordListItem, 0)
response.Response.RecordCountInfo = &dnspod.RecordCountInfo{
TotalCount: common.Uint64Ptr(uint64(0)),
}
return response, nil
}
response.Response.RecordList = api.dnspodRecords[*request.Domain]
response.Response.RecordCountInfo = &dnspod.RecordCountInfo{
TotalCount: common.Uint64Ptr(uint64(len(api.dnspodRecords[*request.Domain]))),
}
return response, nil
}
func (api *mockAPIService) CreateRecord(request *dnspod.CreateRecordRequest) (response *dnspod.CreateRecordResponse, err error) {
randomRecordId := RandUint64()
if _, exist := api.dnspodRecords[*request.Domain]; !exist {
api.dnspodRecords[*request.Domain] = make([]*dnspod.RecordListItem, 0)
}
if request.TTL == nil {
request.TTL = common.Uint64Ptr(300)
}
api.dnspodRecords[*request.Domain] = append(api.dnspodRecords[*request.Domain], &dnspod.RecordListItem{
RecordId: common.Uint64Ptr(randomRecordId),
Value: request.Value,
TTL: request.TTL,
Name: request.SubDomain,
Line: request.RecordLine,
LineId: request.RecordLineId,
Type: request.RecordType,
})
return response, nil
}
func (api *mockAPIService) DeleteRecord(request *dnspod.DeleteRecordRequest) (response *dnspod.DeleteRecordResponse, err error) {
result := make([]*dnspod.RecordListItem, 0)
if _, exist := api.dnspodRecords[*request.Domain]; !exist {
return response, nil
}
for _, zoneRecord := range api.dnspodRecords[*request.Domain] {
deleteflag := false
if request.RecordId != nil && *request.RecordId == *zoneRecord.RecordId {
deleteflag = true
}
if !deleteflag {
result = append(result, zoneRecord)
}
}
api.dnspodRecords[*request.Domain] = result
return response, nil
}
func (api *mockAPIService) ModifyRecord(request *dnspod.ModifyRecordRequest) (response *dnspod.ModifyRecordResponse, err error) {
if _, exist := api.dnspodRecords[*request.Domain]; !exist {
return response, nil
}
for _, zoneRecord := range api.dnspodRecords[*request.Domain] {
if *zoneRecord.RecordId != *request.RecordId {
continue
}
zoneRecord.Type = request.RecordType
zoneRecord.Name = request.SubDomain
zoneRecord.Value = request.Value
zoneRecord.TTL = request.TTL
}
return response, nil
}
var letterRunes = []byte("abcdefghijklmnopqrstuvwxyz")
func RandStringRunes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func RandUint64() uint64 {
return rand.Uint64()
}

View File

@ -0,0 +1,78 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudapi
import (
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
)
type readonlyAPIService struct {
defaultTencentAPIService
}
func NewReadOnlyAPIService(region string, rate int, secretId string, secretKey string, internetEndpoint bool) *readonlyAPIService {
apiService := NewTencentAPIService(region, rate, secretId, secretKey, internetEndpoint)
tencentAPIService := &readonlyAPIService{
*apiService,
}
return tencentAPIService
}
////////////////////////////////////////////////////////////////
// PrivateDns API
////////////////////////////////////////////////////////////////
func (api *readonlyAPIService) CreatePrivateZoneRecord(request *privatedns.CreatePrivateZoneRecordRequest) (response *privatedns.CreatePrivateZoneRecordResponse, err error) {
apiAction := CreatePrivateZoneRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}
func (api *readonlyAPIService) DeletePrivateZoneRecord(request *privatedns.DeletePrivateZoneRecordRequest) (response *privatedns.DeletePrivateZoneRecordResponse, err error) {
apiAction := DeletePrivateZoneRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}
func (api *readonlyAPIService) ModifyPrivateZoneRecord(request *privatedns.ModifyPrivateZoneRecordRequest) (response *privatedns.ModifyPrivateZoneRecordResponse, err error) {
apiAction := ModifyPrivateZoneRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}
////////////////////////////////////////////////////////////////
// DnsPod API
////////////////////////////////////////////////////////////////
func (api *readonlyAPIService) CreateRecord(request *dnspod.CreateRecordRequest) (response *dnspod.CreateRecordResponse, err error) {
apiAction := CreateRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}
func (api *readonlyAPIService) DeleteRecord(request *dnspod.DeleteRecordRequest) (response *dnspod.DeleteRecordResponse, err error) {
apiAction := DeleteRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}
func (api *readonlyAPIService) ModifyRecord(request *dnspod.ModifyRecordRequest) (response *dnspod.ModifyRecordResponse, err error) {
apiAction := ModifyRecord
APIRecord(apiAction, JsonWrapper(request), "dryRun")
return response, nil
}

View File

@ -0,0 +1,279 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudapi
import (
"encoding/json"
"fmt"
"net"
"time"
log "github.com/sirupsen/logrus"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
)
type defaultTencentAPIService struct {
RetryDefault int
TaskCheckInterval time.Duration
ClientSetService TencentClientSetService
}
func NewTencentAPIService(region string, rate int, secretId string, secretKey string, internetEndpoint bool) *defaultTencentAPIService {
tencentAPIService := &defaultTencentAPIService{
RetryDefault: 3,
TaskCheckInterval: 3 * time.Second,
ClientSetService: NewTencentClientSetService(region, rate, secretId, secretKey, internetEndpoint),
}
return tencentAPIService
}
////////////////////////////////////////////////////////////////
// PrivateDns API
////////////////////////////////////////////////////////////////
func (api *defaultTencentAPIService) CreatePrivateZoneRecord(request *privatedns.CreatePrivateZoneRecordRequest) (response *privatedns.CreatePrivateZoneRecordResponse, err error) {
apiAction := CreatePrivateZoneRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.PrivateDnsCli(apiAction.Name)
if response, err = client.CreatePrivateZoneRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) DeletePrivateZoneRecord(request *privatedns.DeletePrivateZoneRecordRequest) (response *privatedns.DeletePrivateZoneRecordResponse, err error) {
apiAction := DeletePrivateZoneRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.PrivateDnsCli(apiAction.Name)
if response, err = client.DeletePrivateZoneRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) ModifyPrivateZoneRecord(request *privatedns.ModifyPrivateZoneRecordRequest) (response *privatedns.ModifyPrivateZoneRecordResponse, err error) {
apiAction := ModifyPrivateZoneRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.PrivateDnsCli(apiAction.Name)
if response, err = client.ModifyPrivateZoneRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) DescribePrivateZoneList(request *privatedns.DescribePrivateZoneListRequest) (response *privatedns.DescribePrivateZoneListResponse, err error) {
apiAction := DescribePrivateZoneList
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.PrivateDnsCli(apiAction.Name)
if response, err = client.DescribePrivateZoneList(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) DescribePrivateZoneRecordList(request *privatedns.DescribePrivateZoneRecordListRequest) (response *privatedns.DescribePrivateZoneRecordListResponse, err error) {
apiAction := DescribePrivateZoneRecordList
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.PrivateDnsCli(apiAction.Name)
if response, err = client.DescribePrivateZoneRecordList(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
////////////////////////////////////////////////////////////////
// DnsPod API
////////////////////////////////////////////////////////////////
func (api *defaultTencentAPIService) DescribeDomainList(request *dnspod.DescribeDomainListRequest) (response *dnspod.DescribeDomainListResponse, err error) {
apiAction := DescribeDomainList
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.DnsPodCli(apiAction.Name)
if response, err = client.DescribeDomainList(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) DescribeRecordList(request *dnspod.DescribeRecordListRequest) (response *dnspod.DescribeRecordListResponse, err error) {
apiAction := DescribeRecordList
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.DnsPodCli(apiAction.Name)
if response, err = client.DescribeRecordList(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) CreateRecord(request *dnspod.CreateRecordRequest) (response *dnspod.CreateRecordResponse, err error) {
apiAction := CreateRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.DnsPodCli(apiAction.Name)
if response, err = client.CreateRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) DeleteRecord(request *dnspod.DeleteRecordRequest) (response *dnspod.DeleteRecordResponse, err error) {
apiAction := DeleteRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.DnsPodCli(apiAction.Name)
if response, err = client.DeleteRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
func (api *defaultTencentAPIService) ModifyRecord(request *dnspod.ModifyRecordRequest) (response *dnspod.ModifyRecordResponse, err error) {
apiAction := ModifyRecord
for times := 1; times <= api.RetryDefault; times++ {
client := api.ClientSetService.DnsPodCli(apiAction.Name)
if response, err = client.ModifyRecord(request); err != nil {
requestJson := JsonWrapper(request)
if retry := dealWithError(apiAction, requestJson, err); retry == false || times == api.RetryDefault {
APIErrorRecord(apiAction, requestJson, JsonWrapper(response), err)
return nil, err
}
continue
}
break
}
APIRecord(apiAction, JsonWrapper(request), JsonWrapper(response))
return response, nil
}
////////////////////////////////////////////////////////////////
// API Error Report
////////////////////////////////////////////////////////////////
func dealWithError(action Action, request string, err error) bool {
log.Errorf("dealWithError %s/%s request: %s, error: %s.", action.Service, action.Name, request, err.Error())
if sdkError, ok := err.(*errors.TencentCloudSDKError); ok {
if sdkError.Code == "RequestLimitExceeded" {
return true
} else if sdkError.Code == "InternalError" || sdkError.Code == "ClientError.HttpStatusCodeError" {
return false
} else if sdkError.Code == "ClientError.NetworkError" {
return false
} else if sdkError.Code == "AuthFailure.UnauthorizedOperation" || sdkError.Code == "UnauthorizedOperation.CamNoAuth" {
return false
}
return false
}
if _, ok := err.(net.Error); ok {
return true
}
return false
}
func APIErrorRecord(apiAction Action, request string, response string, err error) {
log.Infof(fmt.Sprintf("APIError API: %s/%s Request: %s, Response: %s, Error: %s", apiAction.Service, apiAction.Name, request, response, err.Error()))
}
func APIRecord(apiAction Action, request string, response string) {
message := fmt.Sprintf("APIRecord API: %s/%s Request: %s, Response: %s", apiAction.Service, apiAction.Name, request, response)
if apiAction.ReadOnly {
//log.Infof(message)
} else {
log.Infof(message)
}
}
func JsonWrapper(obj interface{}) string {
if jsonStr, jsonErr := json.Marshal(obj); jsonErr == nil {
return string(jsonStr)
}
return "json_format_error"
}

View File

@ -0,0 +1,281 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tencentcloud
import (
"fmt"
"strconv"
"strings"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
// DnsPod For Public Dns
func (p *TencentCloudProvider) dnsRecords() ([]*endpoint.Endpoint, error) {
recordsList, err := p.recordsForDNS()
if err != nil {
return nil, err
}
endpoints := make([]*endpoint.Endpoint, 0)
recordMap := groupDomainRecordList(recordsList)
for _, recordList := range recordMap {
name := getDnsDomain(*recordList.RecordList[0].Name, *recordList.Domain.Name)
recordType := *recordList.RecordList[0].Type
ttl := *recordList.RecordList[0].TTL
var targets []string
for _, record := range recordList.RecordList {
targets = append(targets, *record.Value)
}
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...))
}
return endpoints, nil
}
func (p *TencentCloudProvider) recordsForDNS() (map[uint64]*RecordListGroup, error) {
domainList, err := p.getDomainList()
if err != nil {
return nil, err
}
recordListGroup := make(map[uint64]*RecordListGroup, 0)
for _, domain := range domainList {
records, err := p.getDomainRecordList(*domain.Name)
if err != nil {
return nil, err
}
for _, record := range records {
if *record.Type == "TXT" && strings.HasPrefix(*record.Value, "heritage=") {
record.Value = common.StringPtr(fmt.Sprintf(`"%s"`, *record.Value))
}
}
recordListGroup[*domain.DomainId] = &RecordListGroup{
Domain: domain,
RecordList: records,
}
}
return recordListGroup, nil
}
func (p *TencentCloudProvider) getDomainList() ([]*dnspod.DomainListItem, error) {
request := dnspod.NewDescribeDomainListRequest()
request.Offset = common.Int64Ptr(0)
request.Limit = common.Int64Ptr(3000)
domainList := make([]*dnspod.DomainListItem, 0)
totalCount := int64(100)
for *request.Offset < totalCount {
response, err := p.apiService.DescribeDomainList(request)
if err != nil {
return nil, err
}
if response.Response.DomainList != nil && len(response.Response.DomainList) > 0 {
if !p.domainFilter.IsConfigured() {
domainList = append(domainList, response.Response.DomainList...)
} else {
for _, domain := range response.Response.DomainList {
if p.domainFilter.Match(*domain.Name) {
domainList = append(domainList, domain)
}
}
}
}
totalCount = int64(*response.Response.DomainCountInfo.AllTotal)
request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.DomainList)))
}
return domainList, nil
}
func (p *TencentCloudProvider) getDomainRecordList(domain string) ([]*dnspod.RecordListItem, error) {
request := dnspod.NewDescribeRecordListRequest()
request.Domain = common.StringPtr(domain)
request.Offset = common.Uint64Ptr(0)
request.Limit = common.Uint64Ptr(3000)
domainList := make([]*dnspod.RecordListItem, 0)
totalCount := uint64(100)
for *request.Offset < totalCount {
response, err := p.apiService.DescribeRecordList(request)
if err != nil {
return nil, err
}
if response.Response.RecordList != nil && len(response.Response.RecordList) > 0 {
for _, record := range response.Response.RecordList {
if *record.Name == "@" && *record.Type == "NS" { // Special Record, Skip it.
continue
}
domainList = append(domainList, record)
}
}
totalCount = *response.Response.RecordCountInfo.TotalCount
request.Offset = common.Uint64Ptr(*request.Offset + uint64(len(response.Response.RecordList)))
}
return domainList, nil
}
type RecordListGroup struct {
Domain *dnspod.DomainListItem
RecordList []*dnspod.RecordListItem
}
func (p *TencentCloudProvider) applyChangesForDNS(changes *plan.Changes) error {
recordsGroupMap, err := p.recordsForDNS()
if err != nil {
return err
}
zoneNameIDMapper := provider.ZoneIDName{}
for _, recordsGroup := range recordsGroupMap {
if recordsGroup.Domain.DomainId != nil {
zoneNameIDMapper.Add(strconv.FormatUint(*recordsGroup.Domain.DomainId, 10), *recordsGroup.Domain.Name)
}
}
// Apply Change Delete
deleteEndpoints := make(map[string][]uint64)
for _, change := range [][]*endpoint.Endpoint{changes.Delete, changes.UpdateOld} {
for _, deleteChange := range change {
if zoneId, _ := zoneNameIDMapper.FindZone(deleteChange.DNSName); zoneId != "" {
zoneIdString, _ := strconv.ParseUint(zoneId, 10, 64)
recordListGroup := recordsGroupMap[zoneIdString]
for _, domainRecord := range recordListGroup.RecordList {
subDomain := getSubDomain(*recordListGroup.Domain.Name, deleteChange)
if *domainRecord.Name == subDomain && *domainRecord.Type == deleteChange.RecordType {
for _, target := range deleteChange.Targets {
if *domainRecord.Value == target {
if _, exist := deleteEndpoints[*recordListGroup.Domain.Name]; !exist {
deleteEndpoints[*recordListGroup.Domain.Name] = make([]uint64, 0)
}
deleteEndpoints[*recordListGroup.Domain.Name] = append(deleteEndpoints[*recordListGroup.Domain.Name], *domainRecord.RecordId)
}
}
}
}
}
}
}
if err := p.deleteRecords(deleteEndpoints); err != nil {
return err
}
// Apply Change Create
createEndpoints := make(map[string][]*endpoint.Endpoint)
for zoneId := range zoneNameIDMapper {
createEndpoints[zoneId] = make([]*endpoint.Endpoint, 0)
}
for _, change := range [][]*endpoint.Endpoint{changes.Create, changes.UpdateNew} {
for _, createChange := range change {
if zoneId, _ := zoneNameIDMapper.FindZone(createChange.DNSName); zoneId != "" {
createEndpoints[zoneId] = append(createEndpoints[zoneId], createChange)
}
}
}
if err := p.createRecord(recordsGroupMap, createEndpoints); err != nil {
return err
}
return nil
}
func (p *TencentCloudProvider) createRecord(zoneMap map[uint64]*RecordListGroup, endpointsMap map[string][]*endpoint.Endpoint) error {
for zoneId, endpoints := range endpointsMap {
zoneIdString, _ := strconv.ParseUint(zoneId, 10, 64)
domain := zoneMap[zoneIdString]
for _, endpoint := range endpoints {
for _, target := range endpoint.Targets {
if endpoint.RecordType == "TXT" && strings.HasPrefix(target, `"heritage=`) {
target = strings.Trim(target, `"`)
}
if err := p.createRecords(domain.Domain, endpoint, target); err != nil {
return err
}
}
}
}
return nil
}
func (p *TencentCloudProvider) createRecords(domain *dnspod.DomainListItem, endpoint *endpoint.Endpoint, target string) error {
request := dnspod.NewCreateRecordRequest()
request.Domain = common.StringPtr(*domain.Name)
request.RecordType = common.StringPtr(endpoint.RecordType)
request.Value = common.StringPtr(target)
request.SubDomain = common.StringPtr(getSubDomain(*domain.Name, endpoint))
if endpoint.RecordTTL.IsConfigured() {
request.TTL = common.Uint64Ptr(uint64(endpoint.RecordTTL))
}
request.RecordLine = common.StringPtr("默认")
if _, err := p.apiService.CreateRecord(request); err != nil {
return err
}
return nil
}
func (p *TencentCloudProvider) deleteRecords(RecordIdsMap map[string][]uint64) error {
for domain, recordIds := range RecordIdsMap {
if len(recordIds) == 0 {
continue
}
if err := p.deleteRecord(domain, recordIds); err != nil {
return err
}
}
return nil
}
func (p *TencentCloudProvider) deleteRecord(domain string, recordIds []uint64) error {
request := dnspod.NewDeleteRecordRequest()
request.Domain = common.StringPtr(domain)
for _, recordId := range recordIds {
request.RecordId = common.Uint64Ptr(recordId)
if _, err := p.apiService.DeleteRecord(request); err != nil {
return err
}
}
return nil
}
func groupDomainRecordList(recordListGroup map[uint64]*RecordListGroup) (endpointMap map[string]*RecordListGroup) {
endpointMap = make(map[string]*RecordListGroup)
for _, recordGroup := range recordListGroup {
for _, record := range recordGroup.RecordList {
key := fmt.Sprintf("%s:%s.%s", *record.Type, *record.Name, *recordGroup.Domain.Name)
if *record.Name == TencentCloudEmptyPrefix {
key = fmt.Sprintf("%s:%s", *record.Type, *recordGroup.Domain.Name)
}
if _, exist := endpointMap[key]; !exist {
endpointMap[key] = &RecordListGroup{
Domain: recordGroup.Domain,
RecordList: make([]*dnspod.RecordListItem, 0),
}
}
endpointMap[key].RecordList = append(endpointMap[key].RecordList, record)
}
}
return endpointMap
}

View File

@ -0,0 +1,319 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tencentcloud
import (
"fmt"
"strings"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
// PrivateZone For Internal Dns
func (p *TencentCloudProvider) privateZoneRecords() ([]*endpoint.Endpoint, error) {
privateZones, err := p.recordForPrivateZone()
if err != nil {
return nil, err
}
endpoints := make([]*endpoint.Endpoint, 0)
recordMap := groupPrivateZoneRecords(privateZones)
for _, recordList := range recordMap {
name := getDnsDomain(*recordList.RecordList[0].SubDomain, *recordList.Zone.Domain)
recordType := *recordList.RecordList[0].RecordType
ttl := *recordList.RecordList[0].TTL
var targets []string
for _, record := range recordList.RecordList {
targets = append(targets, *record.RecordValue)
}
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...))
}
return endpoints, nil
}
func (p *TencentCloudProvider) recordForPrivateZone() (map[string]*PrivateZoneRecordListGroup, error) {
privateZones, err := p.getPrivateZones()
if err != nil {
return nil, err
}
recordListGroup := make(map[string]*PrivateZoneRecordListGroup, 0)
for _, zone := range privateZones {
records, err := p.getPrivateZoneRecords(*zone.ZoneId)
if err != nil {
return nil, err
}
for _, record := range records {
if *record.RecordType == "TXT" && strings.HasPrefix(*record.RecordValue, "heritage=") {
record.RecordValue = common.StringPtr(fmt.Sprintf("\"%s\"", *record.RecordValue))
}
}
recordListGroup[*zone.ZoneId] = &PrivateZoneRecordListGroup{
Zone: zone,
RecordList: records,
}
}
return recordListGroup, nil
}
func (p *TencentCloudProvider) getPrivateZones() ([]*privatedns.PrivateZone, error) {
filters := make([]*privatedns.Filter, 1)
filters[0] = &privatedns.Filter{
Name: common.StringPtr("Vpc"),
Values: []*string{
common.StringPtr(p.vpcID),
},
}
if p.zoneIDFilter.IsConfigured() {
zoneIDs := make([]*string, len(p.zoneIDFilter.ZoneIDs))
for index, zoneId := range p.zoneIDFilter.ZoneIDs {
zoneIDs[index] = common.StringPtr(zoneId)
}
filters = append(filters, &privatedns.Filter{
Name: common.StringPtr("ZoneId"),
Values: zoneIDs,
})
}
request := privatedns.NewDescribePrivateZoneListRequest()
request.Filters = filters
request.Offset = common.Int64Ptr(0)
request.Limit = common.Int64Ptr(100)
privateZones := make([]*privatedns.PrivateZone, 0)
totalCount := int64(100)
for *request.Offset < totalCount {
response, err := p.apiService.DescribePrivateZoneList(request)
if err != nil {
return nil, err
}
if response.Response.PrivateZoneSet != nil && len(response.Response.PrivateZoneSet) > 0 {
privateZones = append(privateZones, response.Response.PrivateZoneSet...)
}
totalCount = *response.Response.TotalCount
request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.PrivateZoneSet)))
}
privateZonesFilter := make([]*privatedns.PrivateZone, 0)
for _, privateZone := range privateZones {
if p.domainFilter.IsConfigured() && !p.domainFilter.Match(*privateZone.Domain) {
continue
}
privateZonesFilter = append(privateZonesFilter, privateZone)
}
return privateZonesFilter, nil
}
func (p *TencentCloudProvider) getPrivateZoneRecords(zoneId string) ([]*privatedns.PrivateZoneRecord, error) {
request := privatedns.NewDescribePrivateZoneRecordListRequest()
request.ZoneId = common.StringPtr(zoneId)
request.Offset = common.Int64Ptr(0)
request.Limit = common.Int64Ptr(100)
privateZoneRecords := make([]*privatedns.PrivateZoneRecord, 0)
totalCount := int64(100)
for *request.Offset < totalCount {
response, err := p.apiService.DescribePrivateZoneRecordList(request)
if err != nil {
return nil, err
}
if response.Response.RecordSet != nil && len(response.Response.RecordSet) > 0 {
privateZoneRecords = append(privateZoneRecords, response.Response.RecordSet...)
}
totalCount = *response.Response.TotalCount
request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.RecordSet)))
}
return privateZoneRecords, nil
}
type PrivateZoneRecordListGroup struct {
Zone *privatedns.PrivateZone
RecordList []*privatedns.PrivateZoneRecord
}
// Returns nil if the operation was successful or an error if the operation failed.
func (p *TencentCloudProvider) applyChangesForPrivateZone(changes *plan.Changes) error {
zoneGroups, err := p.recordForPrivateZone()
if err != nil {
return err
}
// In PrivateDns Service. A Zone has at least one record. The last rule cannot be deleted.
for _, zoneGroup := range zoneGroups {
if !containsBaseRecord(zoneGroup.RecordList) {
err := p.createPrivateZoneRecord(zoneGroup.Zone, &endpoint.Endpoint{
DNSName: *zoneGroup.Zone.Domain,
RecordType: "TXT",
}, "tencent_provider_record")
if err != nil {
return err
}
}
}
zoneNameIDMapper := provider.ZoneIDName{}
for _, zoneGroup := range zoneGroups {
if zoneGroup.Zone.ZoneId != nil {
zoneNameIDMapper.Add(*zoneGroup.Zone.ZoneId, *zoneGroup.Zone.Domain)
}
}
// Apply Change Delete
deleteEndpoints := make(map[string][]string)
for _, change := range [][]*endpoint.Endpoint{changes.Delete, changes.UpdateOld} {
for _, deleteChange := range change {
if zoneId, _ := zoneNameIDMapper.FindZone(deleteChange.DNSName); zoneId != "" {
zoneGroup := zoneGroups[zoneId]
for _, zoneRecord := range zoneGroup.RecordList {
subDomain := getSubDomain(*zoneGroup.Zone.Domain, deleteChange)
if *zoneRecord.SubDomain == subDomain && *zoneRecord.RecordType == deleteChange.RecordType {
for _, target := range deleteChange.Targets {
if *zoneRecord.RecordValue == target {
if _, exist := deleteEndpoints[zoneId]; !exist {
deleteEndpoints[zoneId] = make([]string, 0)
}
deleteEndpoints[zoneId] = append(deleteEndpoints[zoneId], *zoneRecord.RecordId)
}
}
}
}
}
}
}
if err := p.deletePrivateZoneRecords(deleteEndpoints); err != nil {
return err
}
// Apply Change Create
createEndpoints := make(map[string][]*endpoint.Endpoint)
for _, change := range [][]*endpoint.Endpoint{changes.Create, changes.UpdateNew} {
for _, createChange := range change {
if zoneId, _ := zoneNameIDMapper.FindZone(createChange.DNSName); zoneId != "" {
if _, exist := createEndpoints[zoneId]; !exist {
createEndpoints[zoneId] = make([]*endpoint.Endpoint, 0)
}
createEndpoints[zoneId] = append(createEndpoints[zoneId], createChange)
}
}
}
if err := p.createPrivateZoneRecords(zoneGroups, createEndpoints); err != nil {
return err
}
return nil
}
func containsBaseRecord(records []*privatedns.PrivateZoneRecord) bool {
for _, record := range records {
if *record.SubDomain == TencentCloudEmptyPrefix && *record.RecordType == "TXT" && *record.RecordValue == "tencent_provider_record" {
return true
}
}
return false
}
func (p *TencentCloudProvider) createPrivateZoneRecords(zoneGroups map[string]*PrivateZoneRecordListGroup, endpointsMap map[string][]*endpoint.Endpoint) error {
for zoneId, endpoints := range endpointsMap {
zoneGroup := zoneGroups[zoneId]
for _, endpoint := range endpoints {
for _, target := range endpoint.Targets {
if endpoint.RecordType == "TXT" && strings.HasPrefix(target, "\"heritage=") {
target = strings.Trim(target, "\"")
}
if err := p.createPrivateZoneRecord(zoneGroup.Zone, endpoint, target); err != nil {
return err
}
}
}
}
return nil
}
func (p *TencentCloudProvider) deletePrivateZoneRecords(zoneRecordIdsMap map[string][]string) error {
for zoneId, zoneRecordIds := range zoneRecordIdsMap {
if len(zoneRecordIds) == 0 {
continue
}
if err := p.deletePrivateZoneRecord(zoneId, zoneRecordIds); err != nil {
return err
}
}
return nil
}
func (p *TencentCloudProvider) createPrivateZoneRecord(zone *privatedns.PrivateZone, endpoint *endpoint.Endpoint, target string) error {
request := privatedns.NewCreatePrivateZoneRecordRequest()
request.ZoneId = common.StringPtr(*zone.ZoneId)
request.RecordType = common.StringPtr(endpoint.RecordType)
request.RecordValue = common.StringPtr(target)
request.SubDomain = common.StringPtr(getSubDomain(*zone.Domain, endpoint))
if endpoint.RecordTTL.IsConfigured() {
request.TTL = common.Int64Ptr(int64(endpoint.RecordTTL))
}
if _, err := p.apiService.CreatePrivateZoneRecord(request); err != nil {
return err
}
return nil
}
func (p *TencentCloudProvider) deletePrivateZoneRecord(zoneId string, zoneRecordIds []string) error {
recordIds := make([]*string, len(zoneRecordIds))
for index, recordId := range zoneRecordIds {
recordIds[index] = common.StringPtr(recordId)
}
request := privatedns.NewDeletePrivateZoneRecordRequest()
request.ZoneId = common.StringPtr(zoneId)
request.RecordIdSet = recordIds
if _, err := p.apiService.DeletePrivateZoneRecord(request); err != nil {
return err
}
return nil
}
func groupPrivateZoneRecords(zoneRecords map[string]*PrivateZoneRecordListGroup) (endpointMap map[string]*PrivateZoneRecordListGroup) {
endpointMap = make(map[string]*PrivateZoneRecordListGroup)
for _, recordGroup := range zoneRecords {
for _, record := range recordGroup.RecordList {
key := fmt.Sprintf("%s:%s.%s", *record.RecordType, *record.SubDomain, *recordGroup.Zone.Domain)
if *record.SubDomain == TencentCloudEmptyPrefix {
key = fmt.Sprintf("%s:%s", *record.RecordType, *recordGroup.Zone.Domain)
}
if _, exist := endpointMap[key]; !exist {
endpointMap[key] = &PrivateZoneRecordListGroup{
Zone: recordGroup.Zone,
RecordList: make([]*privatedns.PrivateZoneRecord, 0),
}
}
endpointMap[key].RecordList = append(endpointMap[key].RecordList, record)
}
}
return endpointMap
}

View File

@ -0,0 +1,122 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tencentcloud
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/tencentcloud/cloudapi"
)
const (
TencentCloudEmptyPrefix = "@"
DefaultAPIRate = 9
)
func NewTencentCloudProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, configFile string, zoneType string, dryRun bool) (*TencentCloudProvider, error) {
cfg := tencentCloudConfig{}
if configFile != "" {
contents, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("failed to read Tencent Cloud config file '%s': %w", configFile, err)
}
err = json.Unmarshal(contents, &cfg)
if err != nil {
return nil, fmt.Errorf("failed to parse Tencent Cloud config file '%s': %w", configFile, err)
}
}
var apiService cloudapi.TencentAPIService = cloudapi.NewTencentAPIService(cfg.RegionId, DefaultAPIRate, cfg.SecretId, cfg.SecretKey, cfg.InternetEndpoint)
if dryRun {
apiService = cloudapi.NewReadOnlyAPIService(cfg.RegionId, DefaultAPIRate, cfg.SecretId, cfg.SecretKey, cfg.InternetEndpoint)
}
tencentCloudProvider := &TencentCloudProvider{
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
apiService: apiService,
vpcID: cfg.VPCId,
privateZone: zoneType == "private",
}
return tencentCloudProvider, nil
}
type TencentCloudProvider struct {
provider.BaseProvider
logger *log.Logger
apiService cloudapi.TencentAPIService
domainFilter endpoint.DomainFilter
zoneIDFilter provider.ZoneIDFilter // Private Zone only
vpcID string // Private Zone only
privateZone bool
}
type tencentCloudConfig struct {
RegionId string `json:"regionId" yaml:"regionId"`
SecretId string `json:"secretId" yaml:"secretId"`
SecretKey string `json:"secretKey" yaml:"secretKey"`
VPCId string `json:"vpcId" yaml:"vpcId"`
InternetEndpoint bool `json:"internetEndpoint" yaml:"internetEndpoint"`
}
func (p *TencentCloudProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
if p.privateZone {
return p.privateZoneRecords()
}
return p.dnsRecords()
}
func (p *TencentCloudProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if !changes.HasChanges() {
return nil
}
log.Infof("apply changes. %s", cloudapi.JsonWrapper(changes))
if p.privateZone {
return p.applyChangesForPrivateZone(changes)
}
return p.applyChangesForDNS(changes)
}
func getSubDomain(domain string, endpoint *endpoint.Endpoint) string {
name := endpoint.DNSName
name = name[:len(name)-len(domain)]
name = strings.TrimSuffix(name, ".")
if name == "" {
return TencentCloudEmptyPrefix
}
return name
}
func getDnsDomain(subDomain string, domain string) string {
if subDomain == TencentCloudEmptyPrefix {
return domain
}
return subDomain + "." + domain
}

View File

@ -0,0 +1,404 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tencentcloud
import (
"context"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/tencentcloud/cloudapi"
"testing"
privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
)
func NewMockTencentCloudProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneType string) *TencentCloudProvider {
cfg := tencentCloudConfig{
//SecretId: "",
//SecretKey: "",
RegionId: "ap-shanghai",
VPCId: "vpc-abcdefg",
}
zoneId1 := common.StringPtr(cloudapi.RandStringRunes(8))
privateZones := []*privatedns.PrivateZone{
{
ZoneId: zoneId1,
Domain: common.StringPtr("external-dns-test.com"),
VpcSet: []*privatedns.VpcInfo{
{
UniqVpcId: common.StringPtr("vpc-abcdefg"),
Region: common.StringPtr("ap-shanghai"),
},
},
},
}
zoneRecordId1 := common.StringPtr(cloudapi.RandStringRunes(8))
zoneRecordId2 := common.StringPtr(cloudapi.RandStringRunes(8))
privateZoneRecords := map[string][]*privatedns.PrivateZoneRecord{
*zoneId1: {
{
ZoneId: zoneId1,
RecordId: zoneRecordId1,
SubDomain: common.StringPtr("nginx"),
RecordType: common.StringPtr("TXT"),
RecordValue: common.StringPtr("heritage=external-dns,external-dns/owner=default"),
TTL: common.Int64Ptr(300),
},
{
ZoneId: zoneId1,
RecordId: zoneRecordId2,
SubDomain: common.StringPtr("nginx"),
RecordType: common.StringPtr("A"),
RecordValue: common.StringPtr("10.10.10.10"),
TTL: common.Int64Ptr(300),
},
},
}
dnsDomainId1 := common.Uint64Ptr(cloudapi.RandUint64())
dnsPodDomains := []*dnspod.DomainListItem{
{
DomainId: dnsDomainId1,
Name: common.StringPtr("external-dns-test.com"),
},
}
dnsDomainRecordId1 := common.Uint64Ptr(cloudapi.RandUint64())
dnsDomainRecordId2 := common.Uint64Ptr(cloudapi.RandUint64())
dnspodRecords := map[string][]*dnspod.RecordListItem{
"external-dns-test.com": {
{
RecordId: dnsDomainRecordId1,
Value: common.StringPtr("heritage=external-dns,external-dns/owner=default"),
Name: common.StringPtr("nginx"),
Type: common.StringPtr("TXT"),
TTL: common.Uint64Ptr(300),
},
{
RecordId: dnsDomainRecordId2,
Name: common.StringPtr("nginx"),
Type: common.StringPtr("A"),
Value: common.StringPtr("10.10.10.10"),
TTL: common.Uint64Ptr(300),
},
},
}
var apiService cloudapi.TencentAPIService = cloudapi.NewMockService(privateZones, privateZoneRecords, dnsPodDomains, dnspodRecords)
tencentCloudProvider := &TencentCloudProvider{
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
apiService: apiService,
vpcID: cfg.VPCId,
privateZone: zoneType == "private",
}
return tencentCloudProvider
}
func TestTencentPrivateProvider_Records(t *testing.T) {
p := NewMockTencentCloudProvider(endpoint.NewDomainFilter([]string{"external-dns-test.com"}), provider.NewZoneIDFilter([]string{}), "private")
endpoints, err := p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Create、UpdateOld、UpdateNew、Delete
// The base record will be created.
changes := &plan.Changes{
Create: []*endpoint.Endpoint{
{
DNSName: "redis.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("4.3.2.1"),
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "nginx.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("10.10.10.10"),
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("1.2.3.4", "5.6.7.8"),
},
},
Delete: []*endpoint.Endpoint{
{
DNSName: "nginx.external-dns-test.com",
RecordType: "TXT",
RecordTTL: 300,
Targets: endpoint.NewTargets("\"heritage=external-dns,external-dns/owner=default\""),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 3 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete one target
changes = &plan.Changes{
Delete: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("5.6.7.8"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 3 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete another target
changes = &plan.Changes{
Create: []*endpoint.Endpoint{
{
DNSName: "redis.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("5.6.7.8"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 3 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete another target
changes = &plan.Changes{
Delete: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("1.2.3.4"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
}
func TestTencentPublicProvider_Records(t *testing.T) {
p := NewMockTencentCloudProvider(endpoint.NewDomainFilter([]string{"external-dns-test.com"}), provider.NewZoneIDFilter([]string{}), "public")
endpoints, err := p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Create、UpdateOld、UpdateNew、Delete
changes := &plan.Changes{
Create: []*endpoint.Endpoint{
{
DNSName: "redis.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("4.3.2.1"),
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "nginx.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("10.10.10.10"),
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("1.2.3.4", "5.6.7.8"),
},
},
Delete: []*endpoint.Endpoint{
{
DNSName: "nginx.external-dns-test.com",
RecordType: "TXT",
RecordTTL: 300,
Targets: endpoint.NewTargets("\"heritage=external-dns,external-dns/owner=default\""),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete one target
changes = &plan.Changes{
Delete: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("5.6.7.8"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete another target
changes = &plan.Changes{
Create: []*endpoint.Endpoint{
{
DNSName: "redis.external-dns-test.com",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("5.6.7.8"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 2 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
// Test for Delete another target
changes = &plan.Changes{
Delete: []*endpoint.Endpoint{
{
DNSName: "tencent.external-dns-test.com",
RecordType: "A",
RecordTTL: 600,
Targets: endpoint.NewTargets("1.2.3.4"),
},
},
}
if err := p.ApplyChanges(context.Background(), changes); err != nil {
t.Errorf("Failed to get records: %v", err)
}
endpoints, err = p.Records(context.Background())
if err != nil {
t.Errorf("Failed to get records: %v", err)
} else {
if len(endpoints) != 1 {
t.Errorf("Incorrect number of records: %d", len(endpoints))
}
for _, endpoint := range endpoints {
t.Logf("Endpoint for %+v", *endpoint)
}
}
}

View File

@ -43,3 +43,11 @@ func (f ZoneIDFilter) Match(zoneID string) bool {
return false
}
// IsConfigured returns true if DomainFilter is configured, false otherwise
func (f ZoneIDFilter) IsConfigured() bool {
if len(f.ZoneIDs) == 1 {
return f.ZoneIDs[0] != ""
}
return len(f.ZoneIDs) > 0
}