Initial support for Alibaba Cloud

This commit is contained in:
Li Yi 2018-07-22 16:53:36 +08:00
parent 4633573564
commit 1db16f35af
5 changed files with 1681 additions and 1 deletions

View File

@ -0,0 +1,360 @@
# Setting up ExternalDNS for Services on Alibaba Cloud
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Alibaba Cloud. Make sure to use **>=0.4** version of ExternalDNS for this tutorial
## RAM Permissions
```json
{
"Version": "1",
"Statement": [
{
"Action": "alidns:AddDomainRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "alidns:DeleteDomainRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "alidns:UpdateDomainRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "alidns:DescribeDomainRecords",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:AddZoneRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:DeleteZoneRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:UpdateZoneRecord",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:DescribeZoneRecords",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:DescribeZones",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "pvtz:DescribeZoneInfo",
"Resource": "*",
"Effect": "Allow"
}
]
}
```
When running on Alibaba Cloud, you need to make sure that your nodes (on which External DNS runs) have the RAM instance profile with the above RAM role assigned.
## Set up a Alibaba Cloud DNS service or Private Zone service
Alibaba Cloud DNS Service is the domain name resolution and management service for public access. It routes access from end-users to the designated web app.
Alibaba Cloud Private Zone is the domain name resolution and management service for VPC internal access.
*If you prefer to try-out ExternalDNS in one of the existing domain or zone you can skip this step*
Create a DNS domain which will contain the managed DNS records. For public DNS service, the domain name should be valid and owned by yourself.
```console
$ aliyun alidns AddDomain --DomainName "external-dns-test.com"
```
Make a note of the ID of the hosted zone you just created.
```console
$ aliyun alidns DescribeDomains --KeyWord="external-dns-test.com" | jq -r '.Domains.Domain[0].DomainId'
```
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
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=alibabacloud
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --alibaba-cloud-zone=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-identifier
```
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
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: extensions/v1beta1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
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=alibabacloud
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --alibaba-cloud-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-identifier
```
## Arguments
This list is not the full list, but a few arguments that where chosen.
### alibabacloud-zone-type
`alibabacloud-zone-type` allows filtering for private and public zones
* If value is `public`, it will sync with records in Alibaba Cloud DNS Service
* If value is `private`, it will sync with records in Alibaba Cloud Private Zone Service
## Verify ExternalDNS works (Ingress example)
Create an ingress resource manifest file.
> For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object.
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: foo
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
rules:
- host: foo.external-dns-test.com
http:
paths:
- backend:
serviceName: foo
servicePort: 80
```
## Verify ExternalDNS works (Service example)
Create the following sample application to test that ExternalDNS works.
> For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.com.
spec:
type: LoadBalancer
ports:
- port: 80
name: http
targetPort: 80
selector:
app: nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: http
```
After roughly two minutes check that a corresponding DNS record for your service was created.
```console
$ aliyun aliyun alidns DescribeDomainRecords --DomainName=external-dns-test.com
{
"PageNumber": 1,
"TotalCount": 1,
"PageSize": 20,
"RequestId": "1DBEF426-F771-46C7-9802-4989E9C94EE8",
"DomainRecords": {
"Record": [
{
"RR": "nginx",
"Status": "ENABLE",
"Value": "1.2.3.4",
"Weight": 1,
"RecordId": "3994015629411328",
"Type": "A",
"DomainName": "external-dns-test.com",
"Locked": false,
"Line": "default",
"TTL": 600
}
{
"RR": "nginx",
"Status": "ENABLE",
"Value": "heritage=external-dns;external-dns/owner=my-identifier",
"Weight": 1,
"RecordId": "3994015629411329",
"Type": "TTL",
"DomainName": "external-dns-test.com",
"Locked": false,
"Line": "default",
"TTL": 600
}
]
}
}
```
Note created TXT record alongside ALIAS record. TXT record signifies that the corresponding ALIAS record is managed by ExternalDNS. This makes ExternalDNS safe for running in environments where there are other records managed via other means.
Let's check that we can resolve this DNS name. We'll ask the nameservers assigned to your zone first.
```console
$ dig nginx.external-dns-test.com.
```
If you hooked up your DNS zone with its parent zone correctly you can use `curl` to access your site.
```console
$ curl nginx.external-dns-test.com.
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```
## Custom TTL
The default DNS record TTL (Time-To-Live) is 300 seconds. You can customize this value by setting the annotation `external-dns.alpha.kubernetes.io/ttl`.
e.g., modify the service manifest YAML file above:
```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/ttl: 60
spec:
...
```
This will set the DNS record's TTL to 60 seconds.
## Clean up
Make sure to delete all Service objects before terminating the cluster so all load balancers get cleaned up correctly.
```console
$ kubectl delete service nginx
```
Give ExternalDNS some time to clean up the DNS records for you. Then delete the hosted zone if you created one for the testing purpose.
```console
$ aliyun alidns DeleteDomain --DomainName external-dns-test.com
```

View File

@ -96,6 +96,8 @@ func main() {
var p provider.Provider
switch cfg.Provider {
case "alibabacloud":
p, err = provider.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
case "aws":
p, err = provider.NewAWSProvider(
provider.AWSConfig{

View File

@ -52,6 +52,8 @@ type Config struct {
GoogleProject string
DomainFilter []string
ZoneIDFilter []string
AlibabaCloudConfigFile string
AlibabaCloudZoneType string
AWSZoneType string
AWSAssumeRole string
AWSMaxChangeCount int
@ -109,6 +111,7 @@ var defaultConfig = &Config{
Provider: "",
GoogleProject: "",
DomainFilter: []string{},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSAssumeRole: "",
AWSMaxChangeCount: 4000,
@ -199,10 +202,12 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("connector-source-server", "The server to connect for connector source, valid only when using connector source").Default(defaultConfig.ConnectorSourceServer).StringVar(&cfg.ConnectorSourceServer)
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, designate, coredns, skydns, inmemory, pdns, oci, exoscale, linode)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns", "oci", "exoscale", "linode")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, designate, coredns, skydns, inmemory, pdns, oci, exoscale, linode)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "alibabacloud", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns", "oci", "exoscale", "linode")
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("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("alibaba-cloud-config-file", "When using the Alibaba Cloud provider, specify the Alibaba Cloud configuration file (required when --provider=alibabacloud").Default(defaultConfig.AlibabaCloudConfigFile).StringVar(&cfg.AlibabaCloudConfigFile)
app.Flag("alibaba-cloud-zone-type", "When using the Alibaba Cloud provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AlibabaCloudZoneType).EnumVar(&cfg.AlibabaCloudZoneType, "", "public", "private")
app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")
app.Flag("aws-assume-role", "When using the AWS provider, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole)
app.Flag("aws-max-change-count", "When using the AWS provider, set the maximum number of changes that will be applied.").Default(strconv.Itoa(defaultConfig.AWSMaxChangeCount)).IntVar(&cfg.AWSMaxChangeCount)

928
provider/alibaba_cloud.go Normal file
View File

@ -0,0 +1,928 @@
/*
Copyright 2017 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 provider
import (
"fmt"
"io/ioutil"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/pvtz"
"github.com/denverdino/aliyungo/metadata"
"strings"
)
const (
defaultAlibabaCloudRecordTTL = 600
defaultAlibabaCloudPrivateZoneRecordTTL = 60
defaultAlibabaCloudPageSize = 50
nullHostAlibabaCloud = "@"
)
// AlibabaCloudDNSAPI is a minimal implementation of DNS API that we actually use, used primarily for unit testing.
// See https://help.aliyun.com/document_detail/29739.html for descriptions of all of its methods.
type AlibabaCloudDNSAPI interface {
AddDomainRecord(request *alidns.AddDomainRecordRequest) (response *alidns.AddDomainRecordResponse, err error)
DeleteDomainRecord(request *alidns.DeleteDomainRecordRequest) (response *alidns.DeleteDomainRecordResponse, err error)
UpdateDomainRecord(request *alidns.UpdateDomainRecordRequest) (response *alidns.UpdateDomainRecordResponse, err error)
DescribeDomainRecords(request *alidns.DescribeDomainRecordsRequest) (response *alidns.DescribeDomainRecordsResponse, err error)
}
// AlibabaCloudDNSAPI is a minimal implementation of Private Zone API that we actually use, used primarily for unit testing.
// See https://help.aliyun.com/document_detail/66234.html for descriptions of all of its methods.
type AlibabaCloudPrivateZoneAPI interface {
AddZoneRecord(request *pvtz.AddZoneRecordRequest) (response *pvtz.AddZoneRecordResponse, err error)
DeleteZoneRecord(request *pvtz.DeleteZoneRecordRequest) (response *pvtz.DeleteZoneRecordResponse, err error)
UpdateZoneRecord(request *pvtz.UpdateZoneRecordRequest) (response *pvtz.UpdateZoneRecordResponse, err error)
DescribeZoneRecords(request *pvtz.DescribeZoneRecordsRequest) (response *pvtz.DescribeZoneRecordsResponse, err error)
DescribeZones(request *pvtz.DescribeZonesRequest) (response *pvtz.DescribeZonesResponse, err error)
DescribeZoneInfo(request *pvtz.DescribeZoneInfoRequest) (response *pvtz.DescribeZoneInfoResponse, err error)
}
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
type AlibabaCloudProvider struct {
domainFilter DomainFilter
zoneIDFilter ZoneIDFilter // Private Zone only
zoneTypeFilter ZoneTypeFilter
MaxChangeCount int
EvaluateTargetHealth bool
AssumeRole string
vpcID string // Private Zone only
dryRun bool
dnsClient AlibabaCloudDNSAPI
pvtzClient AlibabaCloudPrivateZoneAPI
privateZone bool
}
type alibabaCloudConfig struct {
RegionID string `json:"regionId" yaml:"regionId"`
AccessKeyID string `json:"accessKeyId" yaml:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret" yaml:"accessKeySecret"`
VPCID string `json:"vpcId" yaml:"vpcId"`
RoleName string `json:"-" yaml:"-"` // For ECS RAM role only
}
// NewAlibabaCloudProvider creates a new Alibaba Cloud provider.
//
// Returns the provider or an error if a provider could not be created.
func NewAlibabaCloudProvider(configFile string, domainFilter DomainFilter, zoneIDFileter ZoneIDFilter, zoneType string, dryRun bool) (*AlibabaCloudProvider, error) {
cfg := alibabaCloudConfig{}
if configFile != "" {
contents, err := ioutil.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("Failed to read Alibaba Cloud config file '%s': %v", configFile, err)
}
err = yaml.Unmarshal(contents, &cfg)
if err != nil {
return nil, fmt.Errorf("Failed to parse Alibaba Cloud config file '%s': %v", configFile, err)
}
} else {
// Load config from Metadata Service
m := metadata.NewMetaData(nil)
roleName := ""
var err error
if roleName, err = m.RoleName(); err != nil {
return nil, fmt.Errorf("Failed to get role name from Metadata Service: %v", err)
}
vpcID, err := m.VpcID()
if err != nil {
return nil, fmt.Errorf("Failed to get VPC ID from Metadata Service: %v", err)
}
regionID, err := m.Region()
if err != nil {
return nil, fmt.Errorf("Failed to get Region ID from Metadata Service: %v", err)
}
cfg.RegionID = regionID
cfg.RoleName = roleName
cfg.VPCID = vpcID
}
// Public DNS service
var dnsClient AlibabaCloudDNSAPI
var err error
if cfg.RoleName == "" {
dnsClient, err = alidns.NewClientWithAccessKey(
cfg.RegionID,
cfg.AccessKeyID,
cfg.AccessKeySecret,
)
} else {
dnsClient, err = alidns.NewClientWithEcsRamRole(
cfg.RegionID,
cfg.RoleName,
)
}
if err != nil {
return nil, fmt.Errorf("failed to create Alibaba Cloud DNS client: %v", err)
}
// Private DNS service
var pvtzClient AlibabaCloudPrivateZoneAPI
if cfg.RoleName == "" {
pvtzClient, err = pvtz.NewClientWithAccessKey(
"cn-hangzhou", // The Private Zone location is fixed
cfg.AccessKeyID,
cfg.AccessKeySecret,
)
} else {
pvtzClient, err = pvtz.NewClientWithEcsRamRole(
"cn-hangzhou", // The Private Zone location is fixed
cfg.RoleName,
)
}
provider := &AlibabaCloudProvider{
domainFilter: domainFilter,
zoneIDFilter: zoneIDFileter,
vpcID: cfg.VPCID,
dryRun: dryRun,
dnsClient: dnsClient,
pvtzClient: pvtzClient,
privateZone: (zoneType == "private"),
}
return provider, nil
}
// Records gets the current records.
//
// Returns the current records or an error if the operation failed.
func (p *AlibabaCloudProvider) Records() (endpoints []*endpoint.Endpoint, err error) {
if p.privateZone {
endpoints, err = p.privateZoneRecords()
} else {
endpoints, err = p.recordsForDNS()
}
return endpoints, err
}
// ApplyChanges applies the given changes.
//
// Returns nil if the operation was successful or an error if the operation failed.
func (p *AlibabaCloudProvider) ApplyChanges(changes *plan.Changes) error {
if changes == nil || len(changes.Create)+len(changes.Delete)+len(changes.UpdateNew) == 0 {
// No op
return nil
}
if p.privateZone {
return p.applyChangesForPrivateZone(changes)
} else {
return p.applyChangesForDNS(changes)
}
}
func (p *AlibabaCloudProvider) getDNSName(rr, domain string) string {
if rr == nullHostAlibabaCloud {
return domain
} else {
return rr + "." + domain
}
}
// recordsForDNS gets the current records.
//
// Returns the current records or an error if the operation failed.
func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint, _ error) {
records, err := p.records()
if err != nil {
return nil, err
}
for _, recordList := range p.groupRecords(records) {
name := p.getDNSName(recordList[0].RR, recordList[0].DomainName)
recordType := recordList[0].Type
ttl := recordList[0].TTL
if ttl == defaultAlibabaCloudRecordTTL {
ttl = 0
}
var targets []string
for _, record := range recordList {
target := record.Value
if recordType == "TXT" {
target = p.unescapeTXTRecordValue(target)
}
targets = append(targets, target)
}
ep := endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...)
endpoints = append(endpoints, ep)
}
return endpoints, nil
}
func getNextPageNumber(pageNumber, pageSize, totalCount int) int {
if pageNumber*pageSize >= totalCount {
return 0
} else {
return pageNumber + 1
}
}
func (p *AlibabaCloudProvider) getRecordKey(record alidns.Record) string {
if record.RR == nullHostAlibabaCloud {
return record.Type + ":" + record.DomainName
} else {
return record.Type + ":" + record.RR + "." + record.DomainName
}
}
func (p *AlibabaCloudProvider) getRecordKeyByEndpoint(endpoint *endpoint.Endpoint) string {
return endpoint.RecordType + ":" + endpoint.DNSName
}
func (p *AlibabaCloudProvider) groupRecords(records []alidns.Record) (endpointMap map[string][]alidns.Record) {
endpointMap = make(map[string][]alidns.Record)
for _, record := range records {
key := p.getRecordKey(record)
recordList := endpointMap[key]
endpointMap[key] = append(recordList, record)
}
return endpointMap
}
func (p *AlibabaCloudProvider) records() ([]alidns.Record, error) {
log.Debug("Retrieving Alibaba Cloud DNS Domain Records")
var results []alidns.Record
for _, domainName := range p.domainFilter.filters {
request := alidns.CreateDescribeDomainRecordsRequest()
request.DomainName = domainName
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
request.PageNumber = "1"
for {
response, err := p.dnsClient.DescribeDomainRecords(request)
if err != nil {
log.Errorf("Failed to describe domain records for Alibaba Cloud DNS: %v", err)
return nil, err
}
for _, record := range response.DomainRecords.Record {
domainName := record.DomainName
recordType := record.Type
if !p.domainFilter.Match(domainName) {
continue
}
if !supportedRecordType(recordType) {
continue
}
//TODO filter Locked record
results = append(results, record)
}
nextPage := getNextPageNumber(response.PageNumber, defaultAlibabaCloudPageSize, response.TotalCount)
if nextPage == 0 {
break
} else {
request.PageNumber = requests.NewInteger(nextPage)
}
}
}
log.Infof("Found %d Alibaba Cloud DNS record(s).", len(results))
return results, nil
}
func (p *AlibabaCloudProvider) applyChangesForDNS(changes *plan.Changes) error {
log.Debugf("ApplyChanges to Alibaba Cloud DNS: %++v", *changes)
records, err := p.records()
if err != nil {
return err
}
recordMap := p.groupRecords(records)
p.createRecords(changes.Create)
p.deleteRecords(recordMap, changes.Delete)
p.updateRecords(recordMap, changes.UpdateNew)
return nil
}
func (p *AlibabaCloudProvider) escapeTXTRecordValue(value string) string {
// For unsupported chars
return value
}
func (p *AlibabaCloudProvider) unescapeTXTRecordValue(value string) string {
if strings.HasPrefix(value, "heritage=") {
return fmt.Sprintf("\"%s\"", strings.Replace(value, ";", ",", -1))
}
return value
}
func (p *AlibabaCloudProvider) createRecord(endpoint *endpoint.Endpoint, target string) error {
rr, domain := p.splitDNSName(endpoint)
request := alidns.CreateAddDomainRecordRequest()
request.DomainName = domain
request.Type = endpoint.RecordType
request.RR = rr
ttl := int(endpoint.RecordTTL)
if ttl != 0 {
request.TTL = requests.NewInteger(ttl)
}
if endpoint.RecordType == "TXT" {
target = p.escapeTXTRecordValue(target)
}
request.Value = target
if p.dryRun {
log.Infof("Dry run: Create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud DNS", endpoint.RecordType, endpoint.DNSName, target, ttl)
return nil
}
response, err := p.dnsClient.AddDomainRecord(request)
if err == nil {
log.Infof("Create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud DNS: Record ID=%s", endpoint.RecordType, endpoint.DNSName, target, ttl, response.RecordId)
} else {
log.Errorf("Failed to create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud DNS: %v", endpoint.RecordType, endpoint.DNSName, target, ttl, err)
}
return err
}
func (p *AlibabaCloudProvider) createRecords(endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
for _, target := range endpoint.Targets {
p.createRecord(endpoint, target)
}
}
return nil
}
func (p *AlibabaCloudProvider) deleteRecord(recordID string) error {
if p.dryRun {
log.Infof("Dry run: Delete record id '%s' in Alibaba Cloud DNS", recordID)
return nil
}
request := alidns.CreateDeleteDomainRecordRequest()
request.RecordId = recordID
response, err := p.dnsClient.DeleteDomainRecord(request)
if err == nil {
log.Infof("Delete record id '%s' in Alibaba Cloud DNS", response.RecordId)
} else {
log.Errorf("Failed to delete record '%s' in Alibaba Cloud DNS: %v", err)
}
return err
}
func (p *AlibabaCloudProvider) updateRecord(record alidns.Record, endpoint *endpoint.Endpoint) error {
request := alidns.CreateUpdateDomainRecordRequest()
request.RecordId = record.RecordId
request.RR = record.RR
request.Type = record.Type
request.Value = record.Value
ttl := int(endpoint.RecordTTL)
if ttl != 0 {
request.TTL = requests.NewInteger(ttl)
}
response, err := p.dnsClient.UpdateDomainRecord(request)
if err == nil {
log.Infof("Update record id '%s' in Alibaba Cloud DNS", response.RecordId)
} else {
log.Errorf("Failed to update record '%s' in Alibaba Cloud DNS: %v", response.RecordId, err)
}
return err
}
func (p *AlibabaCloudProvider) deleteRecords(recordMap map[string][]alidns.Record, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
key := p.getRecordKeyByEndpoint(endpoint)
records := recordMap[key]
found := false
for _, record := range records {
value := record.Value
if record.Type == "TXT" {
value = p.unescapeTXTRecordValue(value)
}
for _, target := range endpoint.Targets {
// Find matched record to delete
if value == target {
p.deleteRecord(record.RecordId)
found = true
break
}
}
}
if !found {
log.Errorf("Failed to find %s record named '%s' to delete for Alibaba Cloud DNS", endpoint.RecordType, endpoint.DNSName)
}
}
return nil
}
func (p *AlibabaCloudProvider) equals(record alidns.Record, endpoint *endpoint.Endpoint) bool {
ttl1 := record.TTL
if ttl1 == defaultAlibabaCloudRecordTTL {
ttl1 = 0
}
ttl2 := int(endpoint.RecordTTL)
if ttl2 == defaultAlibabaCloudRecordTTL {
ttl2 = 0
}
return ttl1 == ttl2
}
func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]alidns.Record, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
key := p.getRecordKeyByEndpoint(endpoint)
records := recordMap[key]
for _, record := range records {
value := record.Value
if record.Type == "TXT" {
value = p.unescapeTXTRecordValue(value)
}
found := false
for _, target := range endpoint.Targets {
// Find matched record to delete
if value == target {
found = true
}
}
if found {
if !p.equals(record, endpoint) {
// Update record
p.updateRecord(record, endpoint)
}
} else {
p.deleteRecord(record.RecordId)
}
}
for _, target := range endpoint.Targets {
if endpoint.RecordType == "TXT" {
target = p.escapeTXTRecordValue(target)
}
found := false
for _, record := range records {
// Find matched record to delete
if record.Value == target {
found = true
}
}
if !found {
p.createRecord(endpoint, target)
}
}
}
return nil
}
func (p *AlibabaCloudProvider) splitDNSName(endpoint *endpoint.Endpoint) (rr string, domain string) {
name := strings.TrimSuffix(endpoint.DNSName, ".")
found := false
for _, filter := range p.domainFilter.filters {
if strings.HasSuffix(name, "."+filter) {
rr = name[0 : len(name)-len(filter)-1]
domain = filter
found = true
break
} else if name == filter {
domain = filter
rr = ""
found = true
}
}
if !found {
idx := strings.Index(name, ".")
if idx >= 0 {
rr = name[0:idx]
domain = name[idx+1:]
} else {
rr = name
domain = ""
}
}
if rr == "" {
rr = nullHostAlibabaCloud
}
return rr, domain
}
func (p *AlibabaCloudProvider) matchVPC(zoneID string) bool {
request := pvtz.CreateDescribeZoneInfoRequest()
request.ZoneId = zoneID
response, err := p.pvtzClient.DescribeZoneInfo(request)
if err != nil {
log.Errorf("Failed to describe zone info %s in Alibaba Cloud DNS: %v", zoneID, err)
return false
}
foundVPC := false
for _, vpc := range response.BindVpcs.Vpc {
if vpc.VpcId == p.vpcID {
foundVPC = true
break
}
}
return foundVPC
}
func (p *AlibabaCloudProvider) privateZones() ([]pvtz.Zone, error) {
var zones []pvtz.Zone
request := pvtz.CreateDescribeZonesRequest()
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
request.PageNumber = "1"
for {
response, err := p.pvtzClient.DescribeZones(request)
if err != nil {
log.Errorf("Failed to describe zones in Alibaba Cloud DNS: %v", err)
return nil, err
}
for _, zone := range response.Zones.Zone {
log.Debugf("Zone: %++v", zone)
if !p.zoneIDFilter.Match(zone.ZoneId) {
continue
}
if !p.domainFilter.Match(zone.ZoneName) {
continue
}
if !p.matchVPC(zone.ZoneId) {
continue
}
zones = append(zones, zone)
}
nextPage := getNextPageNumber(response.PageNumber, defaultAlibabaCloudPageSize, response.TotalItems)
if nextPage == 0 {
break
} else {
request.PageNumber = requests.NewInteger(nextPage)
}
}
return zones, nil
}
type alibabaPrivateZone struct {
pvtz.Zone
records []pvtz.Record
}
func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone, error) {
log.Debug("Retrieving Alibaba Cloud Private Zone records")
result := make(map[string]*alibabaPrivateZone)
recordsCount := 0
zones, err := p.privateZones()
if err != nil {
return nil, err
}
for _, zone := range zones {
request := pvtz.CreateDescribeZoneRecordsRequest()
request.ZoneId = zone.ZoneId
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
request.PageNumber = "1"
var records []pvtz.Record
for {
response, err := p.pvtzClient.DescribeZoneRecords(request)
if err != nil {
log.Errorf("Failed to describe zone record '%s' in Alibaba Cloud DNS: %v", zone.ZoneId, err)
return nil, err
}
for _, record := range response.Records.Record {
recordType := record.Type
if !supportedRecordType(recordType) {
continue
}
//TODO filter Locked
records = append(records, record)
}
nextPage := getNextPageNumber(response.PageNumber, defaultAlibabaCloudPageSize, response.TotalItems)
if nextPage == 0 {
break
} else {
request.PageNumber = requests.NewInteger(nextPage)
}
}
privateZone := alibabaPrivateZone{
Zone: zone,
records: records,
}
recordsCount += len(records)
result[zone.ZoneName] = &privateZone
}
log.Debugf("Found %d Alibaba Cloud Private Zone record(s).", recordsCount)
return result, nil
}
func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPrivateZone) (endpointMap map[string][]pvtz.Record) {
endpointMap = make(map[string][]pvtz.Record)
for _, record := range zone.records {
key := record.Type + ":" + record.Rr
recordList := endpointMap[key]
endpointMap[key] = append(recordList, record)
}
return endpointMap
}
// recordsForPrivateZone gets the current records.
//
// Returns the current records or an error if the operation failed.
func (p *AlibabaCloudProvider) privateZoneRecords() (endpoints []*endpoint.Endpoint, _ error) {
zones, err := p.getPrivateZones()
if err != nil {
return nil, err
}
for _, zone := range zones {
recordMap := p.groupPrivateZoneRecords(zone)
for _, recordList := range recordMap {
name := p.getDNSName(recordList[0].Rr, zone.ZoneName)
recordType := recordList[0].Type
ttl := recordList[0].Ttl
if ttl == defaultAlibabaCloudPrivateZoneRecordTTL {
ttl = 0
}
var targets []string
for _, record := range recordList {
target := record.Value
if recordType == "TXT" {
target = p.unescapeTXTRecordValue(target)
}
targets = append(targets, target)
}
ep := endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...)
endpoints = append(endpoints, ep)
}
}
return endpoints, nil
}
func (p *AlibabaCloudProvider) createPrivateZoneRecord(zones map[string]*alibabaPrivateZone, endpoint *endpoint.Endpoint, target string) error {
rr, domain := p.splitDNSName(endpoint)
zone := zones[domain]
if zone == nil {
err := fmt.Errorf("Failed to find private zone '%s'", domain)
log.Errorf("Failed to create %s record named '%s' to '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, target, err)
return err
}
request := pvtz.CreateAddZoneRecordRequest()
request.ZoneId = zone.ZoneId
request.Type = endpoint.RecordType
request.Rr = rr
ttl := int(endpoint.RecordTTL)
if ttl != 0 {
request.Ttl = requests.NewInteger(ttl)
}
if endpoint.RecordType == "TXT" {
target = p.escapeTXTRecordValue(target)
}
request.Value = target
if p.dryRun {
log.Infof("Dry run: Create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud Private Zone", endpoint.RecordType, endpoint.DNSName, target, ttl)
return nil
}
response, err := p.pvtzClient.AddZoneRecord(request)
if err == nil {
log.Infof("Create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud Private Zone: Record ID=%d", endpoint.RecordType, endpoint.DNSName, target, ttl, response.RecordId)
} else {
log.Errorf("Failed to create %s record named '%s' to '%s' with ttl %d for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, target, ttl, err)
}
return err
}
func (p *AlibabaCloudProvider) createPrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
for _, target := range endpoint.Targets {
p.createPrivateZoneRecord(zones, endpoint, target)
}
}
return nil
}
func (p *AlibabaCloudProvider) deletePrivateZoneRecord(recordID int) error {
if p.dryRun {
log.Infof("Dry run: Delete record id '%d' in Alibaba Cloud Private Zone", recordID)
}
request := pvtz.CreateDeleteZoneRecordRequest()
request.RecordId = requests.NewInteger(recordID)
response, err := p.pvtzClient.DeleteZoneRecord(request)
if err == nil {
log.Infof("Delete record id '%d' in Alibaba Cloud Private Zone", response.RecordId)
} else {
log.Errorf("Failed to delete record '%s' in Alibaba Cloud Private Zone: %v", err)
}
return err
}
func (p *AlibabaCloudProvider) deletePrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
rr, domain := p.splitDNSName(endpoint)
zone := zones[domain]
if zone == nil {
err := fmt.Errorf("Failed to find private zone '%s'", domain)
log.Errorf("Failed to delete %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
continue
}
found := false
for _, record := range zone.records {
if rr == record.Rr && endpoint.RecordType == record.Type {
value := record.Value
if record.Type == "TXT" {
value = p.unescapeTXTRecordValue(value)
}
for _, target := range endpoint.Targets {
// Find matched record to delete
if value == target {
p.deletePrivateZoneRecord(record.RecordId)
found = true
break
}
}
}
}
if !found {
log.Errorf("Failed to find %s record named '%s' to delete for Alibaba Cloud Private Zone", endpoint.RecordType, endpoint.DNSName)
}
}
return nil
}
// ApplyChanges applies the given changes.
//
// Returns nil if the operation was successful or an error if the operation failed.
func (p *AlibabaCloudProvider) applyChangesForPrivateZone(changes *plan.Changes) error {
log.Debugf("ApplyChanges to Alibaba Cloud Private Zone: %++v", *changes)
zones, err := p.getPrivateZones()
if err != nil {
return err
}
for zoneName, zone := range zones {
log.Debugf("%s: %++v", zoneName, zone)
}
p.createPrivateZoneRecords(zones, changes.Create)
p.deletePrivateZoneRecords(zones, changes.Delete)
p.updatePrivateZoneRecords(zones, changes.UpdateNew)
return nil
}
func (p *AlibabaCloudProvider) updatePrivateZoneRecord(record pvtz.Record, endpoint *endpoint.Endpoint) error {
request := pvtz.CreateUpdateZoneRecordRequest()
request.RecordId = requests.NewInteger(record.RecordId)
request.Rr = record.Rr
request.Type = record.Type
request.Value = record.Value
ttl := int(endpoint.RecordTTL)
if ttl != 0 {
request.Ttl = requests.NewInteger(ttl)
}
response, err := p.pvtzClient.UpdateZoneRecord(request)
if err == nil {
log.Infof("Update record id '%d' in Alibaba Cloud Private Zone", response.RecordId)
} else {
log.Errorf("Failed to update record '%d' in Alibaba Cloud Private Zone: %v", response.RecordId, err)
}
return err
}
func (p *AlibabaCloudProvider) equalsPrivateZone(record pvtz.Record, endpoint *endpoint.Endpoint) bool {
ttl1 := record.Ttl
if ttl1 == defaultAlibabaCloudPrivateZoneRecordTTL {
ttl1 = 0
}
ttl2 := int(endpoint.RecordTTL)
if ttl2 == defaultAlibabaCloudPrivateZoneRecordTTL {
ttl2 = 0
}
return ttl1 == ttl2
}
func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
rr, domain := p.splitDNSName(endpoint)
zone := zones[domain]
if zone == nil {
err := fmt.Errorf("Failed to find private zone '%s'", domain)
log.Errorf("Failed to update %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
continue
}
for _, record := range zone.records {
if record.Rr != rr || record.Type != endpoint.RecordType {
continue
}
value := record.Value
if record.Type == "TXT" {
value = p.unescapeTXTRecordValue(value)
}
found := false
for _, target := range endpoint.Targets {
// Find matched record to delete
if value == target {
found = true
break
}
}
if found {
if !p.equalsPrivateZone(record, endpoint) {
// Update record
p.updatePrivateZoneRecord(record, endpoint)
}
} else {
p.deletePrivateZoneRecord(record.RecordId)
}
}
for _, target := range endpoint.Targets {
if endpoint.RecordType == "TXT" {
target = p.escapeTXTRecordValue(target)
}
found := false
for _, record := range zone.records {
if record.Rr != rr || record.Type != endpoint.RecordType {
continue
}
// Find matched record to delete
if record.Value == target {
found = true
break
}
}
if !found {
p.createPrivateZoneRecord(zones, endpoint, target)
}
}
}
return nil
}

View File

@ -0,0 +1,385 @@
/*
Copyright 2017 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 provider
import (
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
"github.com/aliyun/alibaba-cloud-sdk-go/services/pvtz"
"github.com/kubernetes-incubator/external-dns/endpoint"
"testing"
"github.com/kubernetes-incubator/external-dns/plan"
)
type mockAlibabaCloudDNSAPI struct {
records []alidns.Record
}
func NewMockAlibabaCloudDNSAPI() *mockAlibabaCloudDNSAPI {
api := mockAlibabaCloudDNSAPI{}
api.records = []alidns.Record{
alidns.Record{
RecordId: "1",
DomainName: "container-service.top",
Type: "A",
TTL: 300,
RR: "abc",
Value: "1.2.3.4",
},
alidns.Record{
RecordId: "2",
DomainName: "container-service.top",
Type: "TXT",
TTL: 300,
RR: "abc",
Value: "heritage=external-dns;external-dns/owner=default",
},
}
return &api
}
func (m *mockAlibabaCloudDNSAPI) AddDomainRecord(request *alidns.AddDomainRecordRequest) (response *alidns.AddDomainRecordResponse, err error) {
ttl, _ := request.TTL.GetValue()
m.records = append(m.records, alidns.Record{
RecordId: "3",
DomainName: request.DomainName,
Type: request.Type,
TTL: ttl,
RR: request.RR,
Value: request.Value,
})
response = alidns.CreateAddDomainRecordResponse()
return response, nil
}
func (m *mockAlibabaCloudDNSAPI) DeleteDomainRecord(request *alidns.DeleteDomainRecordRequest) (response *alidns.DeleteDomainRecordResponse, err error) {
var result []alidns.Record
for _, record := range m.records {
if record.RecordId != request.RecordId {
result = append(result, record)
}
}
m.records = result
response = alidns.CreateDeleteDomainRecordResponse()
response.RecordId = request.RecordId
return response, nil
}
func (m *mockAlibabaCloudDNSAPI) UpdateDomainRecord(request *alidns.UpdateDomainRecordRequest) (response *alidns.UpdateDomainRecordResponse, err error) {
ttl, _ := request.TTL.GetValue()
for i, _ := range m.records {
if m.records[i].RecordId == request.RecordId {
m.records[i].TTL = ttl
}
}
response = alidns.CreateUpdateDomainRecordResponse()
response.RecordId = request.RecordId
return response, nil
}
func (m *mockAlibabaCloudDNSAPI) DescribeDomainRecords(request *alidns.DescribeDomainRecordsRequest) (response *alidns.DescribeDomainRecordsResponse, err error) {
var result []alidns.Record
for _, record := range m.records {
if record.DomainName == request.DomainName {
result = append(result, record)
}
}
response = alidns.CreateDescribeDomainRecordsResponse()
response.DomainRecords.Record = result
return response, nil
}
type mockAlibabaCloudPrivateZoneAPI struct {
zone pvtz.Zone
records []pvtz.Record
}
func NewMockAlibabaCloudPrivateZoneAPI() *mockAlibabaCloudPrivateZoneAPI {
api := mockAlibabaCloudPrivateZoneAPI{}
api.zone = pvtz.Zone{
ZoneId: "test-zone",
ZoneName: "container-service.top",
}
api.records = []pvtz.Record{
pvtz.Record{
RecordId: 1,
Type: "A",
Ttl: 300,
Rr: "abc",
Value: "1.2.3.4",
},
pvtz.Record{
RecordId: 2,
Type: "TXT",
Ttl: 300,
Rr: "abc",
Value: "heritage=external-dns;external-dns/owner=default",
},
}
return &api
}
func (m *mockAlibabaCloudPrivateZoneAPI) AddZoneRecord(request *pvtz.AddZoneRecordRequest) (response *pvtz.AddZoneRecordResponse, err error) {
ttl, _ := request.Ttl.GetValue()
m.records = append(m.records, pvtz.Record{
RecordId: 3,
Type: request.Type,
Ttl: ttl,
Rr: request.Rr,
Value: request.Value,
})
response = pvtz.CreateAddZoneRecordResponse()
return response, nil
}
func (m *mockAlibabaCloudPrivateZoneAPI) DeleteZoneRecord(request *pvtz.DeleteZoneRecordRequest) (response *pvtz.DeleteZoneRecordResponse, err error) {
recordId, _ := request.RecordId.GetValue()
var result []pvtz.Record
for _, record := range m.records {
if record.RecordId != recordId {
result = append(result, record)
}
}
response = pvtz.CreateDeleteZoneRecordResponse()
return response, nil
}
func (m *mockAlibabaCloudPrivateZoneAPI) UpdateZoneRecord(request *pvtz.UpdateZoneRecordRequest) (response *pvtz.UpdateZoneRecordResponse, err error) {
recordId, _ := request.RecordId.GetValue()
ttl, _ := request.Ttl.GetValue()
for i, _ := range m.records {
if m.records[i].RecordId == recordId {
m.records[i].Ttl = ttl
}
}
response = pvtz.CreateUpdateZoneRecordResponse()
return response, nil
}
func (m *mockAlibabaCloudPrivateZoneAPI) DescribeZoneRecords(request *pvtz.DescribeZoneRecordsRequest) (response *pvtz.DescribeZoneRecordsResponse, err error) {
response = pvtz.CreateDescribeZoneRecordsResponse()
response.Records.Record = append(response.Records.Record, m.records...)
return response, nil
}
func (m *mockAlibabaCloudPrivateZoneAPI) DescribeZones(request *pvtz.DescribeZonesRequest) (response *pvtz.DescribeZonesResponse, err error) {
response = pvtz.CreateDescribeZonesResponse()
response.Zones.Zone = append(response.Zones.Zone, m.zone)
return response, nil
}
func (m *mockAlibabaCloudPrivateZoneAPI) DescribeZoneInfo(request *pvtz.DescribeZoneInfoRequest) (response *pvtz.DescribeZoneInfoResponse, err error) {
response = pvtz.CreateDescribeZoneInfoResponse()
response.ZoneId = m.zone.ZoneId
response.ZoneName = m.zone.ZoneName
return response, nil
}
func newTestAlibabaCloudProvider(private bool) *AlibabaCloudProvider {
cfg := alibabaCloudConfig{
RegionID: "cn-beijing",
AccessKeyID: "xxxxxx",
AccessKeySecret: "xxxxxx",
VPCID: "vpc-xxxxxx",
}
//
//dnsClient, _ := alidns.NewClientWithAccessKey(
// cfg.RegionID,
// cfg.AccessKeyID,
// cfg.AccessKeySecret,
//)
//
//pvtzClient, _ := pvtz.NewClientWithAccessKey(
// "cn-hangzhou",
// cfg.AccessKeyID,
// cfg.AccessKeySecret,
//)
domainFilterTest := NewDomainFilter([]string{"container-service.top.", "example.org"})
return &AlibabaCloudProvider{
domainFilter: domainFilterTest,
vpcID: cfg.VPCID,
dryRun: false,
dnsClient: NewMockAlibabaCloudDNSAPI(),
pvtzClient: &mockAlibabaCloudPrivateZoneAPI{},
privateZone: false,
}
}
func TestAlibabaCloudProvider_Records(t *testing.T) {
p := newTestAlibabaCloudProvider(false)
endpoints, err := p.Records()
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 TestAlibabaCloudProvider_ApplyChanges(t *testing.T) {
p := newTestAlibabaCloudProvider(false)
changes := plan.Changes{
Create: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "xyz.container-service.top",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("4.3.2.1"),
},
},
UpdateNew: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "abc.container-service.top",
RecordType: "A",
RecordTTL: 500,
Targets: endpoint.NewTargets("1.2.3.4", "5.6.7.8"),
},
},
Delete: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "abc.container-service.top",
RecordType: "TXT",
RecordTTL: 300,
Targets: endpoint.NewTargets("\"heritage=external-dns,external-dns/owner=default\""),
},
},
}
p.ApplyChanges(&changes)
endpoints, err := p.Records()
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 TestAlibabaCloudProvider_Records_PrivateZone(t *testing.T) {
p := newTestAlibabaCloudProvider(true)
endpoints, err := p.Records()
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 TestAlibabaCloudProvider_ApplyChanges_PrivateZone(t *testing.T) {
p := newTestAlibabaCloudProvider(true)
changes := plan.Changes{
Create: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "xyz.container-service.top",
RecordType: "A",
RecordTTL: 300,
Targets: endpoint.NewTargets("4.3.2.1"),
},
},
UpdateNew: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "abc.container-service.top",
RecordType: "A",
RecordTTL: 500,
Targets: endpoint.NewTargets("1.2.3.4", "5.6.7.8"),
},
},
Delete: []*endpoint.Endpoint{
&endpoint.Endpoint{
DNSName: "abc.container-service.top",
RecordType: "TXT",
RecordTTL: 300,
Targets: endpoint.NewTargets("\"heritage=external-dns,external-dns/owner=default\""),
},
},
}
p.ApplyChanges(&changes)
endpoints, err := p.Records()
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 TestAlibabaCloudProvider_splitDNSName(t *testing.T) {
p := newTestAlibabaCloudProvider(false)
endpoint := &endpoint.Endpoint{}
endpoint.DNSName = "www.example.org"
rr, domain := p.splitDNSName(endpoint)
if rr != "www" || domain != "example.org" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = ".example.org"
rr, domain = p.splitDNSName(endpoint)
if rr != "@" || domain != "example.org" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = "www"
rr, domain = p.splitDNSName(endpoint)
if rr != "www" || domain != "" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = ""
rr, domain = p.splitDNSName(endpoint)
if rr != "@" || domain != "" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = "_30000._tcp.container-service.top"
rr, domain = p.splitDNSName(endpoint)
if rr != "_30000._tcp" || domain != "container-service.top" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = "container-service.top"
rr, domain = p.splitDNSName(endpoint)
if rr != "@" || domain != "container-service.top" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
}
func TestAlibabaCloudProvider_TXTEndpoint(t *testing.T) {
p := newTestAlibabaCloudProvider(false)
const recordValue = "heritage=external-dns;external-dns/owner=default"
const endpointTarget = "\"heritage=external-dns,external-dns/owner=default\""
if p.normalizedTXTRecordValue(endpointTarget) != recordValue {
t.Errorf("Failed to normalizedTXTRecordValue: %s", p.normalizedTXTRecordValue(endpointTarget))
}
if p.unescapeTXTRecordValue(recordValue) != endpointTarget {
t.Errorf("Failed to unescapeTXTRecordValue: %s", p.unescapeTXTRecordValue(recordValue))
}
}