sigs.k8s.io/external-dns@v0.14.1/provider/tencentcloud/dnspod.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tencentcloud
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
    25  	dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
    26  
    27  	"sigs.k8s.io/external-dns/endpoint"
    28  	"sigs.k8s.io/external-dns/plan"
    29  	"sigs.k8s.io/external-dns/provider"
    30  )
    31  
    32  // DnsPod For Public Dns
    33  
    34  func (p *TencentCloudProvider) dnsRecords() ([]*endpoint.Endpoint, error) {
    35  	recordsList, err := p.recordsForDNS()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	endpoints := make([]*endpoint.Endpoint, 0)
    41  	recordMap := groupDomainRecordList(recordsList)
    42  	for _, recordList := range recordMap {
    43  		name := getDnsDomain(*recordList.RecordList[0].Name, *recordList.Domain.Name)
    44  		recordType := *recordList.RecordList[0].Type
    45  		ttl := *recordList.RecordList[0].TTL
    46  		var targets []string
    47  		for _, record := range recordList.RecordList {
    48  			targets = append(targets, *record.Value)
    49  		}
    50  		endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...))
    51  	}
    52  	return endpoints, nil
    53  }
    54  
    55  func (p *TencentCloudProvider) recordsForDNS() (map[uint64]*RecordListGroup, error) {
    56  	domainList, err := p.getDomainList()
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	recordListGroup := make(map[uint64]*RecordListGroup, 0)
    62  	for _, domain := range domainList {
    63  		records, err := p.getDomainRecordList(*domain.Name)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		for _, record := range records {
    68  			if *record.Type == "TXT" && strings.HasPrefix(*record.Value, "heritage=") {
    69  				record.Value = common.StringPtr(fmt.Sprintf(`"%s"`, *record.Value))
    70  			}
    71  		}
    72  		recordListGroup[*domain.DomainId] = &RecordListGroup{
    73  			Domain:     domain,
    74  			RecordList: records,
    75  		}
    76  	}
    77  	return recordListGroup, nil
    78  }
    79  
    80  func (p *TencentCloudProvider) getDomainList() ([]*dnspod.DomainListItem, error) {
    81  	request := dnspod.NewDescribeDomainListRequest()
    82  	request.Offset = common.Int64Ptr(0)
    83  	request.Limit = common.Int64Ptr(3000)
    84  
    85  	domainList := make([]*dnspod.DomainListItem, 0)
    86  	totalCount := int64(100)
    87  	for *request.Offset < totalCount {
    88  		response, err := p.apiService.DescribeDomainList(request)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		if response.Response.DomainList != nil && len(response.Response.DomainList) > 0 {
    93  			if !p.domainFilter.IsConfigured() {
    94  				domainList = append(domainList, response.Response.DomainList...)
    95  			} else {
    96  				for _, domain := range response.Response.DomainList {
    97  					if p.domainFilter.Match(*domain.Name) {
    98  						domainList = append(domainList, domain)
    99  					}
   100  				}
   101  			}
   102  		}
   103  		totalCount = int64(*response.Response.DomainCountInfo.AllTotal)
   104  		request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.DomainList)))
   105  	}
   106  	return domainList, nil
   107  }
   108  
   109  func (p *TencentCloudProvider) getDomainRecordList(domain string) ([]*dnspod.RecordListItem, error) {
   110  	request := dnspod.NewDescribeRecordListRequest()
   111  	request.Domain = common.StringPtr(domain)
   112  	request.Offset = common.Uint64Ptr(0)
   113  	request.Limit = common.Uint64Ptr(3000)
   114  
   115  	domainList := make([]*dnspod.RecordListItem, 0)
   116  	totalCount := uint64(100)
   117  	for *request.Offset < totalCount {
   118  		response, err := p.apiService.DescribeRecordList(request)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		if response.Response.RecordList != nil && len(response.Response.RecordList) > 0 {
   123  			for _, record := range response.Response.RecordList {
   124  				if *record.Name == "@" && *record.Type == "NS" { // Special Record, Skip it.
   125  					continue
   126  				}
   127  				domainList = append(domainList, record)
   128  			}
   129  		}
   130  		totalCount = *response.Response.RecordCountInfo.TotalCount
   131  		request.Offset = common.Uint64Ptr(*request.Offset + uint64(len(response.Response.RecordList)))
   132  	}
   133  	return domainList, nil
   134  }
   135  
   136  type RecordListGroup struct {
   137  	Domain     *dnspod.DomainListItem
   138  	RecordList []*dnspod.RecordListItem
   139  }
   140  
   141  func (p *TencentCloudProvider) applyChangesForDNS(changes *plan.Changes) error {
   142  	recordsGroupMap, err := p.recordsForDNS()
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	zoneNameIDMapper := provider.ZoneIDName{}
   148  	for _, recordsGroup := range recordsGroupMap {
   149  		if recordsGroup.Domain.DomainId != nil {
   150  			zoneNameIDMapper.Add(strconv.FormatUint(*recordsGroup.Domain.DomainId, 10), *recordsGroup.Domain.Name)
   151  		}
   152  	}
   153  
   154  	// Apply Change Delete
   155  	deleteEndpoints := make(map[string][]uint64)
   156  	for _, change := range [][]*endpoint.Endpoint{changes.Delete, changes.UpdateOld} {
   157  		for _, deleteChange := range change {
   158  			if zoneId, _ := zoneNameIDMapper.FindZone(deleteChange.DNSName); zoneId != "" {
   159  				zoneIdString, _ := strconv.ParseUint(zoneId, 10, 64)
   160  				recordListGroup := recordsGroupMap[zoneIdString]
   161  				for _, domainRecord := range recordListGroup.RecordList {
   162  					subDomain := getSubDomain(*recordListGroup.Domain.Name, deleteChange)
   163  					if *domainRecord.Name == subDomain && *domainRecord.Type == deleteChange.RecordType {
   164  						for _, target := range deleteChange.Targets {
   165  							if *domainRecord.Value == target {
   166  								if _, exist := deleteEndpoints[*recordListGroup.Domain.Name]; !exist {
   167  									deleteEndpoints[*recordListGroup.Domain.Name] = make([]uint64, 0)
   168  								}
   169  								deleteEndpoints[*recordListGroup.Domain.Name] = append(deleteEndpoints[*recordListGroup.Domain.Name], *domainRecord.RecordId)
   170  							}
   171  						}
   172  					}
   173  				}
   174  			}
   175  		}
   176  	}
   177  
   178  	if err := p.deleteRecords(deleteEndpoints); err != nil {
   179  		return err
   180  	}
   181  
   182  	// Apply Change Create
   183  	createEndpoints := make(map[string][]*endpoint.Endpoint)
   184  	for zoneId := range zoneNameIDMapper {
   185  		createEndpoints[zoneId] = make([]*endpoint.Endpoint, 0)
   186  	}
   187  	for _, change := range [][]*endpoint.Endpoint{changes.Create, changes.UpdateNew} {
   188  		for _, createChange := range change {
   189  			if zoneId, _ := zoneNameIDMapper.FindZone(createChange.DNSName); zoneId != "" {
   190  				createEndpoints[zoneId] = append(createEndpoints[zoneId], createChange)
   191  			}
   192  		}
   193  	}
   194  	if err := p.createRecord(recordsGroupMap, createEndpoints); err != nil {
   195  		return err
   196  	}
   197  	return nil
   198  }
   199  
   200  func (p *TencentCloudProvider) createRecord(zoneMap map[uint64]*RecordListGroup, endpointsMap map[string][]*endpoint.Endpoint) error {
   201  	for zoneId, endpoints := range endpointsMap {
   202  		zoneIdString, _ := strconv.ParseUint(zoneId, 10, 64)
   203  		domain := zoneMap[zoneIdString]
   204  		for _, endpoint := range endpoints {
   205  			for _, target := range endpoint.Targets {
   206  				if endpoint.RecordType == "TXT" && strings.HasPrefix(target, `"heritage=`) {
   207  					target = strings.Trim(target, `"`)
   208  				}
   209  				if err := p.createRecords(domain.Domain, endpoint, target); err != nil {
   210  					return err
   211  				}
   212  			}
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  func (p *TencentCloudProvider) createRecords(domain *dnspod.DomainListItem, endpoint *endpoint.Endpoint, target string) error {
   219  	request := dnspod.NewCreateRecordRequest()
   220  
   221  	request.Domain = common.StringPtr(*domain.Name)
   222  	request.RecordType = common.StringPtr(endpoint.RecordType)
   223  	request.Value = common.StringPtr(target)
   224  	request.SubDomain = common.StringPtr(getSubDomain(*domain.Name, endpoint))
   225  	if endpoint.RecordTTL.IsConfigured() {
   226  		request.TTL = common.Uint64Ptr(uint64(endpoint.RecordTTL))
   227  	}
   228  	request.RecordLine = common.StringPtr("默认")
   229  
   230  	if _, err := p.apiService.CreateRecord(request); err != nil {
   231  		return err
   232  	}
   233  	return nil
   234  }
   235  
   236  func (p *TencentCloudProvider) deleteRecords(RecordIdsMap map[string][]uint64) error {
   237  	for domain, recordIds := range RecordIdsMap {
   238  		if len(recordIds) == 0 {
   239  			continue
   240  		}
   241  		if err := p.deleteRecord(domain, recordIds); err != nil {
   242  			return err
   243  		}
   244  	}
   245  	return nil
   246  }
   247  
   248  func (p *TencentCloudProvider) deleteRecord(domain string, recordIds []uint64) error {
   249  	request := dnspod.NewDeleteRecordRequest()
   250  	request.Domain = common.StringPtr(domain)
   251  
   252  	for _, recordId := range recordIds {
   253  		request.RecordId = common.Uint64Ptr(recordId)
   254  		if _, err := p.apiService.DeleteRecord(request); err != nil {
   255  			return err
   256  		}
   257  	}
   258  	return nil
   259  }
   260  
   261  func groupDomainRecordList(recordListGroup map[uint64]*RecordListGroup) (endpointMap map[string]*RecordListGroup) {
   262  	endpointMap = make(map[string]*RecordListGroup)
   263  
   264  	for _, recordGroup := range recordListGroup {
   265  		for _, record := range recordGroup.RecordList {
   266  			key := fmt.Sprintf("%s:%s.%s", *record.Type, *record.Name, *recordGroup.Domain.Name)
   267  			if *record.Name == TencentCloudEmptyPrefix {
   268  				key = fmt.Sprintf("%s:%s", *record.Type, *recordGroup.Domain.Name)
   269  			}
   270  			if _, exist := endpointMap[key]; !exist {
   271  				endpointMap[key] = &RecordListGroup{
   272  					Domain:     recordGroup.Domain,
   273  					RecordList: make([]*dnspod.RecordListItem, 0),
   274  				}
   275  			}
   276  			endpointMap[key].RecordList = append(endpointMap[key].RecordList, record)
   277  		}
   278  	}
   279  
   280  	return endpointMap
   281  }