sigs.k8s.io/external-dns@v0.14.1/provider/tencentcloud/privatedns.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  	"strings"
    22  
    23  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
    24  	privatedns "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns/v20201028"
    25  
    26  	"sigs.k8s.io/external-dns/endpoint"
    27  	"sigs.k8s.io/external-dns/plan"
    28  	"sigs.k8s.io/external-dns/provider"
    29  )
    30  
    31  // PrivateZone For Internal Dns
    32  
    33  func (p *TencentCloudProvider) privateZoneRecords() ([]*endpoint.Endpoint, error) {
    34  	privateZones, err := p.recordForPrivateZone()
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	endpoints := make([]*endpoint.Endpoint, 0)
    40  	recordMap := groupPrivateZoneRecords(privateZones)
    41  	for _, recordList := range recordMap {
    42  		name := getDnsDomain(*recordList.RecordList[0].SubDomain, *recordList.Zone.Domain)
    43  		recordType := *recordList.RecordList[0].RecordType
    44  		ttl := *recordList.RecordList[0].TTL
    45  		var targets []string
    46  		for _, record := range recordList.RecordList {
    47  			targets = append(targets, *record.RecordValue)
    48  		}
    49  		endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), targets...))
    50  	}
    51  	return endpoints, nil
    52  }
    53  
    54  func (p *TencentCloudProvider) recordForPrivateZone() (map[string]*PrivateZoneRecordListGroup, error) {
    55  	privateZones, err := p.getPrivateZones()
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	recordListGroup := make(map[string]*PrivateZoneRecordListGroup, 0)
    61  	for _, zone := range privateZones {
    62  		records, err := p.getPrivateZoneRecords(*zone.ZoneId)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  
    67  		for _, record := range records {
    68  			if *record.RecordType == "TXT" && strings.HasPrefix(*record.RecordValue, "heritage=") {
    69  				record.RecordValue = common.StringPtr(fmt.Sprintf("\"%s\"", *record.RecordValue))
    70  			}
    71  		}
    72  		recordListGroup[*zone.ZoneId] = &PrivateZoneRecordListGroup{
    73  			Zone:       zone,
    74  			RecordList: records,
    75  		}
    76  	}
    77  
    78  	return recordListGroup, nil
    79  }
    80  
    81  func (p *TencentCloudProvider) getPrivateZones() ([]*privatedns.PrivateZone, error) {
    82  	filters := make([]*privatedns.Filter, 1)
    83  	filters[0] = &privatedns.Filter{
    84  		Name: common.StringPtr("Vpc"),
    85  		Values: []*string{
    86  			common.StringPtr(p.vpcID),
    87  		},
    88  	}
    89  
    90  	if p.zoneIDFilter.IsConfigured() {
    91  		zoneIDs := make([]*string, len(p.zoneIDFilter.ZoneIDs))
    92  		for index, zoneId := range p.zoneIDFilter.ZoneIDs {
    93  			zoneIDs[index] = common.StringPtr(zoneId)
    94  		}
    95  		filters = append(filters, &privatedns.Filter{
    96  			Name:   common.StringPtr("ZoneId"),
    97  			Values: zoneIDs,
    98  		})
    99  	}
   100  
   101  	request := privatedns.NewDescribePrivateZoneListRequest()
   102  	request.Filters = filters
   103  	request.Offset = common.Int64Ptr(0)
   104  	request.Limit = common.Int64Ptr(100)
   105  
   106  	privateZones := make([]*privatedns.PrivateZone, 0)
   107  	totalCount := int64(100)
   108  	for *request.Offset < totalCount {
   109  		response, err := p.apiService.DescribePrivateZoneList(request)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		if response.Response.PrivateZoneSet != nil && len(response.Response.PrivateZoneSet) > 0 {
   114  			privateZones = append(privateZones, response.Response.PrivateZoneSet...)
   115  		}
   116  		totalCount = *response.Response.TotalCount
   117  		request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.PrivateZoneSet)))
   118  	}
   119  
   120  	privateZonesFilter := make([]*privatedns.PrivateZone, 0)
   121  	for _, privateZone := range privateZones {
   122  		if !p.domainFilter.Match(*privateZone.Domain) {
   123  			continue
   124  		}
   125  		privateZonesFilter = append(privateZonesFilter, privateZone)
   126  	}
   127  	return privateZonesFilter, nil
   128  }
   129  
   130  func (p *TencentCloudProvider) getPrivateZoneRecords(zoneId string) ([]*privatedns.PrivateZoneRecord, error) {
   131  	request := privatedns.NewDescribePrivateZoneRecordListRequest()
   132  	request.ZoneId = common.StringPtr(zoneId)
   133  	request.Offset = common.Int64Ptr(0)
   134  	request.Limit = common.Int64Ptr(100)
   135  
   136  	privateZoneRecords := make([]*privatedns.PrivateZoneRecord, 0)
   137  	totalCount := int64(100)
   138  	for *request.Offset < totalCount {
   139  		response, err := p.apiService.DescribePrivateZoneRecordList(request)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  		if response.Response.RecordSet != nil && len(response.Response.RecordSet) > 0 {
   144  			privateZoneRecords = append(privateZoneRecords, response.Response.RecordSet...)
   145  		}
   146  		totalCount = *response.Response.TotalCount
   147  		request.Offset = common.Int64Ptr(*request.Offset + int64(len(response.Response.RecordSet)))
   148  	}
   149  	return privateZoneRecords, nil
   150  }
   151  
   152  type PrivateZoneRecordListGroup struct {
   153  	Zone       *privatedns.PrivateZone
   154  	RecordList []*privatedns.PrivateZoneRecord
   155  }
   156  
   157  // Returns nil if the operation was successful or an error if the operation failed.
   158  func (p *TencentCloudProvider) applyChangesForPrivateZone(changes *plan.Changes) error {
   159  	zoneGroups, err := p.recordForPrivateZone()
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	// In PrivateDns Service. A Zone has at least one record. The last rule cannot be deleted.
   165  	for _, zoneGroup := range zoneGroups {
   166  		if !containsBaseRecord(zoneGroup.RecordList) {
   167  			err := p.createPrivateZoneRecord(zoneGroup.Zone, &endpoint.Endpoint{
   168  				DNSName:    *zoneGroup.Zone.Domain,
   169  				RecordType: "TXT",
   170  			}, "tencent_provider_record")
   171  			if err != nil {
   172  				return err
   173  			}
   174  		}
   175  	}
   176  
   177  	zoneNameIDMapper := provider.ZoneIDName{}
   178  	for _, zoneGroup := range zoneGroups {
   179  		if zoneGroup.Zone.ZoneId != nil {
   180  			zoneNameIDMapper.Add(*zoneGroup.Zone.ZoneId, *zoneGroup.Zone.Domain)
   181  		}
   182  	}
   183  
   184  	// Apply Change Delete
   185  	deleteEndpoints := make(map[string][]string)
   186  	for _, change := range [][]*endpoint.Endpoint{changes.Delete, changes.UpdateOld} {
   187  		for _, deleteChange := range change {
   188  			if zoneId, _ := zoneNameIDMapper.FindZone(deleteChange.DNSName); zoneId != "" {
   189  				zoneGroup := zoneGroups[zoneId]
   190  				for _, zoneRecord := range zoneGroup.RecordList {
   191  					subDomain := getSubDomain(*zoneGroup.Zone.Domain, deleteChange)
   192  					if *zoneRecord.SubDomain == subDomain && *zoneRecord.RecordType == deleteChange.RecordType {
   193  						for _, target := range deleteChange.Targets {
   194  							if *zoneRecord.RecordValue == target {
   195  								if _, exist := deleteEndpoints[zoneId]; !exist {
   196  									deleteEndpoints[zoneId] = make([]string, 0)
   197  								}
   198  								deleteEndpoints[zoneId] = append(deleteEndpoints[zoneId], *zoneRecord.RecordId)
   199  							}
   200  						}
   201  					}
   202  				}
   203  			}
   204  		}
   205  	}
   206  
   207  	if err := p.deletePrivateZoneRecords(deleteEndpoints); err != nil {
   208  		return err
   209  	}
   210  
   211  	// Apply Change Create
   212  	createEndpoints := make(map[string][]*endpoint.Endpoint)
   213  	for _, change := range [][]*endpoint.Endpoint{changes.Create, changes.UpdateNew} {
   214  		for _, createChange := range change {
   215  			if zoneId, _ := zoneNameIDMapper.FindZone(createChange.DNSName); zoneId != "" {
   216  				if _, exist := createEndpoints[zoneId]; !exist {
   217  					createEndpoints[zoneId] = make([]*endpoint.Endpoint, 0)
   218  				}
   219  				createEndpoints[zoneId] = append(createEndpoints[zoneId], createChange)
   220  			}
   221  		}
   222  	}
   223  	if err := p.createPrivateZoneRecords(zoneGroups, createEndpoints); err != nil {
   224  		return err
   225  	}
   226  	return nil
   227  }
   228  
   229  func containsBaseRecord(records []*privatedns.PrivateZoneRecord) bool {
   230  	for _, record := range records {
   231  		if *record.SubDomain == TencentCloudEmptyPrefix && *record.RecordType == "TXT" && *record.RecordValue == "tencent_provider_record" {
   232  			return true
   233  		}
   234  	}
   235  	return false
   236  }
   237  
   238  func (p *TencentCloudProvider) createPrivateZoneRecords(zoneGroups map[string]*PrivateZoneRecordListGroup, endpointsMap map[string][]*endpoint.Endpoint) error {
   239  	for zoneId, endpoints := range endpointsMap {
   240  		zoneGroup := zoneGroups[zoneId]
   241  		for _, endpoint := range endpoints {
   242  			for _, target := range endpoint.Targets {
   243  				if endpoint.RecordType == "TXT" && strings.HasPrefix(target, "\"heritage=") {
   244  					target = strings.Trim(target, "\"")
   245  				}
   246  				if err := p.createPrivateZoneRecord(zoneGroup.Zone, endpoint, target); err != nil {
   247  					return err
   248  				}
   249  			}
   250  		}
   251  	}
   252  	return nil
   253  }
   254  
   255  func (p *TencentCloudProvider) deletePrivateZoneRecords(zoneRecordIdsMap map[string][]string) error {
   256  	for zoneId, zoneRecordIds := range zoneRecordIdsMap {
   257  		if len(zoneRecordIds) == 0 {
   258  			continue
   259  		}
   260  		if err := p.deletePrivateZoneRecord(zoneId, zoneRecordIds); err != nil {
   261  			return err
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  func (p *TencentCloudProvider) createPrivateZoneRecord(zone *privatedns.PrivateZone, endpoint *endpoint.Endpoint, target string) error {
   268  	request := privatedns.NewCreatePrivateZoneRecordRequest()
   269  	request.ZoneId = common.StringPtr(*zone.ZoneId)
   270  	request.RecordType = common.StringPtr(endpoint.RecordType)
   271  	request.RecordValue = common.StringPtr(target)
   272  	request.SubDomain = common.StringPtr(getSubDomain(*zone.Domain, endpoint))
   273  	if endpoint.RecordTTL.IsConfigured() {
   274  		request.TTL = common.Int64Ptr(int64(endpoint.RecordTTL))
   275  	}
   276  
   277  	if _, err := p.apiService.CreatePrivateZoneRecord(request); err != nil {
   278  		return err
   279  	}
   280  	return nil
   281  }
   282  
   283  func (p *TencentCloudProvider) deletePrivateZoneRecord(zoneId string, zoneRecordIds []string) error {
   284  	recordIds := make([]*string, len(zoneRecordIds))
   285  	for index, recordId := range zoneRecordIds {
   286  		recordIds[index] = common.StringPtr(recordId)
   287  	}
   288  
   289  	request := privatedns.NewDeletePrivateZoneRecordRequest()
   290  	request.ZoneId = common.StringPtr(zoneId)
   291  	request.RecordIdSet = recordIds
   292  
   293  	if _, err := p.apiService.DeletePrivateZoneRecord(request); err != nil {
   294  		return err
   295  	}
   296  	return nil
   297  }
   298  
   299  func groupPrivateZoneRecords(zoneRecords map[string]*PrivateZoneRecordListGroup) (endpointMap map[string]*PrivateZoneRecordListGroup) {
   300  	endpointMap = make(map[string]*PrivateZoneRecordListGroup)
   301  
   302  	for _, recordGroup := range zoneRecords {
   303  		for _, record := range recordGroup.RecordList {
   304  			key := fmt.Sprintf("%s:%s.%s", *record.RecordType, *record.SubDomain, *recordGroup.Zone.Domain)
   305  			if *record.SubDomain == TencentCloudEmptyPrefix {
   306  				key = fmt.Sprintf("%s:%s", *record.RecordType, *recordGroup.Zone.Domain)
   307  			}
   308  			if _, exist := endpointMap[key]; !exist {
   309  				endpointMap[key] = &PrivateZoneRecordListGroup{
   310  					Zone:       recordGroup.Zone,
   311  					RecordList: make([]*privatedns.PrivateZoneRecord, 0),
   312  				}
   313  			}
   314  			endpointMap[key].RecordList = append(endpointMap[key].RecordList, record)
   315  		}
   316  	}
   317  
   318  	return endpointMap
   319  }