mirror of
				https://github.com/kubernetes-sigs/external-dns.git
				synced 2025-10-31 10:41:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1008 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1008 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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 ibmcloud
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/crn"
 | |
| 	"github.com/IBM/go-sdk-core/v5/core"
 | |
| 	"github.com/IBM/networking-go-sdk/dnsrecordsv1"
 | |
| 	"github.com/IBM/networking-go-sdk/dnssvcsv1"
 | |
| 	"github.com/IBM/networking-go-sdk/zonesv1"
 | |
| 	"gopkg.in/yaml.v2"
 | |
| 
 | |
| 	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/source"
 | |
| )
 | |
| 
 | |
| var proxyTypeNotSupported = map[string]bool{
 | |
| 	"LOC": true,
 | |
| 	"MX":  true,
 | |
| 	"NS":  true,
 | |
| 	"SPF": true,
 | |
| 	"TXT": true,
 | |
| 	"SRV": true,
 | |
| }
 | |
| 
 | |
| var privateTypeSupported = map[string]bool{
 | |
| 	"A":     true,
 | |
| 	"CNAME": true,
 | |
| 	"TXT":   true,
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	// recordCreate is a ChangeAction enum value
 | |
| 	recordCreate = "CREATE"
 | |
| 	// recordDelete is a ChangeAction enum value
 | |
| 	recordDelete = "DELETE"
 | |
| 	// recordUpdate is a ChangeAction enum value
 | |
| 	recordUpdate = "UPDATE"
 | |
| 	// defaultPublicRecordTTL 1 = automatic
 | |
| 	defaultPublicRecordTTL = 1
 | |
| 
 | |
| 	proxyFilter             = "ibmcloud-proxied"
 | |
| 	vpcFilter               = "ibmcloud-vpc"
 | |
| 	zoneStatePendingNetwork = "PENDING_NETWORK_ADD"
 | |
| 	zoneStateActive         = "ACTIVE"
 | |
| )
 | |
| 
 | |
| // Source shadow the interface source.Source. used primarily for unit testing.
 | |
| type Source interface {
 | |
| 	Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error)
 | |
| 	AddEventHandler(context.Context, func())
 | |
| }
 | |
| 
 | |
| // ibmcloudClient is a minimal implementation of DNS API that we actually use, used primarily for unit testing.
 | |
| type ibmcloudClient interface {
 | |
| 	ListAllDDNSRecordsWithContext(ctx context.Context, listAllDNSRecordsOptions *dnsrecordsv1.ListAllDnsRecordsOptions) (result *dnsrecordsv1.ListDnsrecordsResp, response *core.DetailedResponse, err error)
 | |
| 	CreateDNSRecordWithContext(ctx context.Context, createDNSRecordOptions *dnsrecordsv1.CreateDnsRecordOptions) (result *dnsrecordsv1.DnsrecordResp, response *core.DetailedResponse, err error)
 | |
| 	DeleteDNSRecordWithContext(ctx context.Context, deleteDNSRecordOptions *dnsrecordsv1.DeleteDnsRecordOptions) (result *dnsrecordsv1.DeleteDnsrecordResp, response *core.DetailedResponse, err error)
 | |
| 	UpdateDNSRecordWithContext(ctx context.Context, updateDNSRecordOptions *dnsrecordsv1.UpdateDnsRecordOptions) (result *dnsrecordsv1.DnsrecordResp, response *core.DetailedResponse, err error)
 | |
| 	ListDnszonesWithContext(ctx context.Context, listDnszonesOptions *dnssvcsv1.ListDnszonesOptions) (result *dnssvcsv1.ListDnszones, response *core.DetailedResponse, err error)
 | |
| 	GetDnszoneWithContext(ctx context.Context, getDnszoneOptions *dnssvcsv1.GetDnszoneOptions) (result *dnssvcsv1.Dnszone, response *core.DetailedResponse, err error)
 | |
| 	CreatePermittedNetworkWithContext(ctx context.Context, createPermittedNetworkOptions *dnssvcsv1.CreatePermittedNetworkOptions) (result *dnssvcsv1.PermittedNetwork, response *core.DetailedResponse, err error)
 | |
| 	ListResourceRecordsWithContext(ctx context.Context, listResourceRecordsOptions *dnssvcsv1.ListResourceRecordsOptions) (result *dnssvcsv1.ListResourceRecords, response *core.DetailedResponse, err error)
 | |
| 	CreateResourceRecordWithContext(ctx context.Context, createResourceRecordOptions *dnssvcsv1.CreateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error)
 | |
| 	DeleteResourceRecordWithContext(ctx context.Context, deleteResourceRecordOptions *dnssvcsv1.DeleteResourceRecordOptions) (response *core.DetailedResponse, err error)
 | |
| 	UpdateResourceRecordWithContext(ctx context.Context, updateResourceRecordOptions *dnssvcsv1.UpdateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error)
 | |
| 	NewResourceRecordInputRdataRdataARecord(ip string) (model *dnssvcsv1.ResourceRecordInputRdataRdataARecord, err error)
 | |
| 	NewResourceRecordInputRdataRdataCnameRecord(cname string) (model *dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord, err error)
 | |
| 	NewResourceRecordInputRdataRdataTxtRecord(text string) (model *dnssvcsv1.ResourceRecordInputRdataRdataTxtRecord, err error)
 | |
| 	NewResourceRecordUpdateInputRdataRdataARecord(ip string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataARecord, err error)
 | |
| 	NewResourceRecordUpdateInputRdataRdataCnameRecord(cname string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataCnameRecord, err error)
 | |
| 	NewResourceRecordUpdateInputRdataRdataTxtRecord(text string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataTxtRecord, err error)
 | |
| }
 | |
| 
 | |
| type ibmcloudService struct {
 | |
| 	publicZonesService   *zonesv1.ZonesV1
 | |
| 	publicRecordsService *dnsrecordsv1.DnsRecordsV1
 | |
| 	privateDNSService    *dnssvcsv1.DnsSvcsV1
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) ListAllDDNSRecordsWithContext(ctx context.Context, listAllDNSRecordsOptions *dnsrecordsv1.ListAllDnsRecordsOptions) (result *dnsrecordsv1.ListDnsrecordsResp, response *core.DetailedResponse, err error) {
 | |
| 	return i.publicRecordsService.ListAllDnsRecordsWithContext(ctx, listAllDNSRecordsOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) CreateDNSRecordWithContext(ctx context.Context, createDNSRecordOptions *dnsrecordsv1.CreateDnsRecordOptions) (result *dnsrecordsv1.DnsrecordResp, response *core.DetailedResponse, err error) {
 | |
| 	return i.publicRecordsService.CreateDnsRecordWithContext(ctx, createDNSRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) DeleteDNSRecordWithContext(ctx context.Context, deleteDNSRecordOptions *dnsrecordsv1.DeleteDnsRecordOptions) (result *dnsrecordsv1.DeleteDnsrecordResp, response *core.DetailedResponse, err error) {
 | |
| 	return i.publicRecordsService.DeleteDnsRecordWithContext(ctx, deleteDNSRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) UpdateDNSRecordWithContext(ctx context.Context, updateDNSRecordOptions *dnsrecordsv1.UpdateDnsRecordOptions) (result *dnsrecordsv1.DnsrecordResp, response *core.DetailedResponse, err error) {
 | |
| 	return i.publicRecordsService.UpdateDnsRecordWithContext(ctx, updateDNSRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) ListDnszonesWithContext(ctx context.Context, listDnszonesOptions *dnssvcsv1.ListDnszonesOptions) (result *dnssvcsv1.ListDnszones, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.ListDnszonesWithContext(ctx, listDnszonesOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) GetDnszoneWithContext(ctx context.Context, getDnszoneOptions *dnssvcsv1.GetDnszoneOptions) (result *dnssvcsv1.Dnszone, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.GetDnszoneWithContext(ctx, getDnszoneOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) CreatePermittedNetworkWithContext(ctx context.Context, createPermittedNetworkOptions *dnssvcsv1.CreatePermittedNetworkOptions) (result *dnssvcsv1.PermittedNetwork, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.CreatePermittedNetworkWithContext(ctx, createPermittedNetworkOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) ListResourceRecordsWithContext(ctx context.Context, listResourceRecordsOptions *dnssvcsv1.ListResourceRecordsOptions) (result *dnssvcsv1.ListResourceRecords, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.ListResourceRecordsWithContext(ctx, listResourceRecordsOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) CreateResourceRecordWithContext(ctx context.Context, createResourceRecordOptions *dnssvcsv1.CreateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.CreateResourceRecordWithContext(ctx, createResourceRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) DeleteResourceRecordWithContext(ctx context.Context, deleteResourceRecordOptions *dnssvcsv1.DeleteResourceRecordOptions) (response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.DeleteResourceRecordWithContext(ctx, deleteResourceRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) UpdateResourceRecordWithContext(ctx context.Context, updateResourceRecordOptions *dnssvcsv1.UpdateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error) {
 | |
| 	return i.privateDNSService.UpdateResourceRecordWithContext(ctx, updateResourceRecordOptions)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordInputRdataRdataARecord(ip string) (model *dnssvcsv1.ResourceRecordInputRdataRdataARecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordInputRdataRdataARecord(ip)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordInputRdataRdataCnameRecord(cname string) (model *dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordInputRdataRdataCnameRecord(cname)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordInputRdataRdataTxtRecord(text string) (model *dnssvcsv1.ResourceRecordInputRdataRdataTxtRecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordInputRdataRdataTxtRecord(text)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordUpdateInputRdataRdataARecord(ip string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataARecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordUpdateInputRdataRdataARecord(ip)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordUpdateInputRdataRdataCnameRecord(cname string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataCnameRecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordUpdateInputRdataRdataCnameRecord(cname)
 | |
| }
 | |
| 
 | |
| func (i ibmcloudService) NewResourceRecordUpdateInputRdataRdataTxtRecord(text string) (model *dnssvcsv1.ResourceRecordUpdateInputRdataRdataTxtRecord, err error) {
 | |
| 	return i.privateDNSService.NewResourceRecordUpdateInputRdataRdataTxtRecord(text)
 | |
| }
 | |
| 
 | |
| // IBMCloudProvider is an implementation of Provider for IBM Cloud DNS.
 | |
| type IBMCloudProvider struct {
 | |
| 	provider.BaseProvider
 | |
| 	source Source
 | |
| 	Client ibmcloudClient
 | |
| 	// only consider hosted zones managing domains ending in this suffix
 | |
| 	domainFilter     endpoint.DomainFilter
 | |
| 	zoneIDFilter     provider.ZoneIDFilter
 | |
| 	instanceID       string
 | |
| 	privateZone      bool
 | |
| 	proxiedByDefault bool
 | |
| 	DryRun           bool
 | |
| }
 | |
| 
 | |
| type ibmcloudConfig struct {
 | |
| 	Endpoint   string `json:"endpoint" yaml:"endpoint"`
 | |
| 	APIKey     string `json:"apiKey" yaml:"apiKey"`
 | |
| 	CRN        string `json:"instanceCrn" yaml:"instanceCrn"`
 | |
| 	IAMURL     string `json:"iamUrl" yaml:"iamUrl"`
 | |
| 	InstanceID string `json:"-" yaml:"-"`
 | |
| }
 | |
| 
 | |
| // ibmcloudChange differentiates between ChangActions
 | |
| type ibmcloudChange struct {
 | |
| 	Action                string
 | |
| 	PublicResourceRecord  dnsrecordsv1.DnsrecordDetails
 | |
| 	PrivateResourceRecord dnssvcsv1.ResourceRecord
 | |
| }
 | |
| 
 | |
| func getConfig(configFile string) (*ibmcloudConfig, error) {
 | |
| 	contents, err := os.ReadFile(configFile)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to read IBM Cloud config file '%s': %v", configFile, err)
 | |
| 	}
 | |
| 	cfg := &ibmcloudConfig{}
 | |
| 	err = yaml.Unmarshal(contents, &cfg)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to read IBM Cloud config file '%s': %v", configFile, err)
 | |
| 	}
 | |
| 
 | |
| 	return cfg, nil
 | |
| }
 | |
| 
 | |
| func (c *ibmcloudConfig) Validate(authenticator core.Authenticator, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter) (ibmcloudService, bool, error) {
 | |
| 	var service ibmcloudService
 | |
| 	isPrivate := false
 | |
| 	log.Debugf("filters: %v, %v", domainFilter.Filters, zoneIDFilter.ZoneIDs)
 | |
| 	if (len(domainFilter.Filters) == 0 || domainFilter.Filters[0] == "") && zoneIDFilter.ZoneIDs[0] == "" {
 | |
| 		return service, isPrivate, fmt.Errorf("at lease one of filters: 'domain-filter', 'zone-id-filter' needed")
 | |
| 	}
 | |
| 
 | |
| 	crn, err := crn.Parse(c.CRN)
 | |
| 	if err != nil {
 | |
| 		return service, isPrivate, err
 | |
| 	}
 | |
| 	log.Infof("IBM Cloud Service: %s", crn.ServiceName)
 | |
| 	c.InstanceID = crn.ServiceInstance
 | |
| 
 | |
| 	switch {
 | |
| 	case strings.Contains(crn.ServiceName, "internet-svcs"):
 | |
| 		if len(domainFilter.Filters) > 1 || len(zoneIDFilter.ZoneIDs) > 1 {
 | |
| 			return service, isPrivate, fmt.Errorf("for public zone, only one domain id filter or domain name filter allowed")
 | |
| 		}
 | |
| 		var zoneID string
 | |
| 		// Public DNS service
 | |
| 		service.publicZonesService, err = zonesv1.NewZonesV1(&zonesv1.ZonesV1Options{
 | |
| 			Authenticator: authenticator,
 | |
| 			Crn:           core.StringPtr(c.CRN),
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return service, isPrivate, fmt.Errorf("failed to initialize ibmcloud public zones client: %v", err)
 | |
| 		}
 | |
| 		if c.Endpoint != "" {
 | |
| 			service.publicZonesService.SetServiceURL(c.Endpoint)
 | |
| 		}
 | |
| 
 | |
| 		zonesResp, _, err := service.publicZonesService.ListZones(&zonesv1.ListZonesOptions{})
 | |
| 		if err != nil {
 | |
| 			return service, isPrivate, fmt.Errorf("failed to list ibmcloud public zones: %v", err)
 | |
| 		}
 | |
| 		for _, zone := range zonesResp.Result {
 | |
| 			log.Debugf("zoneName: %s, zoneID: %s", *zone.Name, *zone.ID)
 | |
| 			if len(domainFilter.Filters) > 0 && domainFilter.Filters[0] != "" && domainFilter.Match(*zone.Name) {
 | |
| 				log.Debugf("zone %s found.", *zone.ID)
 | |
| 				zoneID = *zone.ID
 | |
| 				break
 | |
| 			}
 | |
| 			if len(zoneIDFilter.ZoneIDs[0]) != 0 && zoneIDFilter.Match(*zone.ID) {
 | |
| 				log.Debugf("zone %s found.", *zone.ID)
 | |
| 				zoneID = *zone.ID
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if len(zoneID) == 0 {
 | |
| 			return service, isPrivate, fmt.Errorf("no matched zone found")
 | |
| 		}
 | |
| 
 | |
| 		service.publicRecordsService, err = dnsrecordsv1.NewDnsRecordsV1(&dnsrecordsv1.DnsRecordsV1Options{
 | |
| 			Authenticator:  authenticator,
 | |
| 			Crn:            core.StringPtr(c.CRN),
 | |
| 			ZoneIdentifier: core.StringPtr(zoneID),
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return service, isPrivate, fmt.Errorf("failed to initialize ibmcloud public records client: %v", err)
 | |
| 		}
 | |
| 		if c.Endpoint != "" {
 | |
| 			service.publicRecordsService.SetServiceURL(c.Endpoint)
 | |
| 		}
 | |
| 	case strings.Contains(crn.ServiceName, "dns-svcs"):
 | |
| 		isPrivate = true
 | |
| 		// Private DNS service
 | |
| 		service.privateDNSService, err = dnssvcsv1.NewDnsSvcsV1(&dnssvcsv1.DnsSvcsV1Options{
 | |
| 			Authenticator: authenticator,
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return service, isPrivate, fmt.Errorf("failed to initialize ibmcloud private records client: %v", err)
 | |
| 		}
 | |
| 		if c.Endpoint != "" {
 | |
| 			service.privateDNSService.SetServiceURL(c.Endpoint)
 | |
| 		}
 | |
| 	default:
 | |
| 		return service, isPrivate, fmt.Errorf("IBM Cloud instance crn is not provided or invalid dns crn : %s", c.CRN)
 | |
| 	}
 | |
| 
 | |
| 	return service, isPrivate, nil
 | |
| }
 | |
| 
 | |
| // NewIBMCloudProvider creates a new IBMCloud provider.
 | |
| //
 | |
| // Returns the provider or an error if a provider could not be created.
 | |
| func NewIBMCloudProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, source source.Source, proxiedByDefault bool, dryRun bool) (*IBMCloudProvider, error) {
 | |
| 	cfg, err := getConfig(configFile)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	authenticator := &core.IamAuthenticator{
 | |
| 		ApiKey: cfg.APIKey,
 | |
| 	}
 | |
| 	if cfg.IAMURL != "" {
 | |
| 		authenticator = &core.IamAuthenticator{
 | |
| 			ApiKey: cfg.APIKey,
 | |
| 			URL:    cfg.IAMURL,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	client, isPrivate, err := cfg.Validate(authenticator, domainFilter, zoneIDFilter)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	provider := &IBMCloudProvider{
 | |
| 		Client:           client,
 | |
| 		source:           source,
 | |
| 		domainFilter:     domainFilter,
 | |
| 		zoneIDFilter:     zoneIDFilter,
 | |
| 		instanceID:       cfg.InstanceID,
 | |
| 		privateZone:      isPrivate,
 | |
| 		proxiedByDefault: proxiedByDefault,
 | |
| 		DryRun:           dryRun,
 | |
| 	}
 | |
| 	return provider, nil
 | |
| }
 | |
| 
 | |
| // Records gets the current records.
 | |
| //
 | |
| // Returns the current records or an error if the operation failed.
 | |
| func (p *IBMCloudProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) {
 | |
| 	if p.privateZone {
 | |
| 		endpoints, err = p.privateRecords(ctx)
 | |
| 	} else {
 | |
| 		endpoints, err = p.publicRecords(ctx)
 | |
| 	}
 | |
| 	return endpoints, err
 | |
| }
 | |
| 
 | |
| // ApplyChanges applies a given set of changes in a given zone.
 | |
| func (p *IBMCloudProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
 | |
| 	log.Debugln("applying change...")
 | |
| 	ibmcloudChanges := []*ibmcloudChange{}
 | |
| 	for _, endpoint := range changes.Create {
 | |
| 		for _, target := range endpoint.Targets {
 | |
| 			ibmcloudChanges = append(ibmcloudChanges, p.newIBMCloudChange(recordCreate, endpoint, target))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for i, desired := range changes.UpdateNew {
 | |
| 		current := changes.UpdateOld[i]
 | |
| 
 | |
| 		add, remove, leave := provider.Difference(current.Targets, desired.Targets)
 | |
| 
 | |
| 		log.Debugf("add: %v, remove: %v, leave: %v", add, remove, leave)
 | |
| 		for _, a := range add {
 | |
| 			ibmcloudChanges = append(ibmcloudChanges, p.newIBMCloudChange(recordCreate, desired, a))
 | |
| 		}
 | |
| 
 | |
| 		for _, a := range leave {
 | |
| 			ibmcloudChanges = append(ibmcloudChanges, p.newIBMCloudChange(recordUpdate, desired, a))
 | |
| 		}
 | |
| 
 | |
| 		for _, a := range remove {
 | |
| 			ibmcloudChanges = append(ibmcloudChanges, p.newIBMCloudChange(recordDelete, current, a))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, endpoint := range changes.Delete {
 | |
| 		for _, target := range endpoint.Targets {
 | |
| 			ibmcloudChanges = append(ibmcloudChanges, p.newIBMCloudChange(recordDelete, endpoint, target))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return p.submitChanges(ctx, ibmcloudChanges)
 | |
| }
 | |
| 
 | |
| // AdjustEndpoints modifies the endpoints as needed by the specific provider
 | |
| func (p *IBMCloudProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoint.Endpoint, error) {
 | |
| 	adjustedEndpoints := []*endpoint.Endpoint{}
 | |
| 	for _, e := range endpoints {
 | |
| 		log.Debugf("adjusting endpont: %v", *e)
 | |
| 		proxied := shouldBeProxied(e, p.proxiedByDefault)
 | |
| 		if proxied {
 | |
| 			e.RecordTTL = 0
 | |
| 		}
 | |
| 		e.SetProviderSpecificProperty(proxyFilter, strconv.FormatBool(proxied))
 | |
| 
 | |
| 		adjustedEndpoints = append(adjustedEndpoints, e)
 | |
| 	}
 | |
| 	return adjustedEndpoints, nil
 | |
| }
 | |
| 
 | |
| // submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
 | |
| func (p *IBMCloudProvider) submitChanges(ctx context.Context, changes []*ibmcloudChange) error {
 | |
| 	// return early if there is nothing to change
 | |
| 	if len(changes) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	log.Debugln("submmiting change...")
 | |
| 	if p.privateZone {
 | |
| 		return p.submitChangesForPrivateDNS(ctx, changes)
 | |
| 	}
 | |
| 	return p.submitChangesForPublicDNS(ctx, changes)
 | |
| }
 | |
| 
 | |
| // submitChangesForPublicDNS takes a zone and a collection of Changes and sends them as a single transaction on public dns.
 | |
| func (p *IBMCloudProvider) submitChangesForPublicDNS(ctx context.Context, changes []*ibmcloudChange) error {
 | |
| 	records, err := p.listAllPublicRecords(ctx)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	for _, change := range changes {
 | |
| 		logFields := log.Fields{
 | |
| 			"record": *change.PublicResourceRecord.Name,
 | |
| 			"type":   *change.PublicResourceRecord.Type,
 | |
| 			"ttl":    *change.PublicResourceRecord.TTL,
 | |
| 			"action": change.Action,
 | |
| 		}
 | |
| 
 | |
| 		if p.DryRun {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		log.WithFields(logFields).Info("Changing record.")
 | |
| 
 | |
| 		if change.Action == recordUpdate {
 | |
| 			recordID := p.getPublicRecordID(records, change.PublicResourceRecord)
 | |
| 			if recordID == "" {
 | |
| 				log.WithFields(logFields).Errorf("failed to find previous record: %v", *change.PublicResourceRecord.Name)
 | |
| 				continue
 | |
| 			}
 | |
| 			p.updateRecord(ctx, "", recordID, change)
 | |
| 		} else if change.Action == recordDelete {
 | |
| 			recordID := p.getPublicRecordID(records, change.PublicResourceRecord)
 | |
| 			if recordID == "" {
 | |
| 				log.WithFields(logFields).Errorf("failed to find previous record: %v", *change.PublicResourceRecord.Name)
 | |
| 				continue
 | |
| 			}
 | |
| 			p.deleteRecord(ctx, "", recordID)
 | |
| 		} else if change.Action == recordCreate {
 | |
| 			p.createRecord(ctx, "", change)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // submitChangesForPrivateDNS takes a zone and a collection of Changes and sends them as a single transaction on private dns.
 | |
| func (p *IBMCloudProvider) submitChangesForPrivateDNS(ctx context.Context, changes []*ibmcloudChange) error {
 | |
| 	zones, err := p.privateZones(ctx)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	// separate into per-zone change sets to be passed to the API.
 | |
| 	changesByPrivateZone := p.changesByPrivateZone(ctx, zones, changes)
 | |
| 
 | |
| 	for zoneID, changes := range changesByPrivateZone {
 | |
| 		records, err := p.listAllPrivateRecords(ctx, zoneID)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		for _, change := range changes {
 | |
| 			logFields := log.Fields{
 | |
| 				"record": *change.PrivateResourceRecord.Name,
 | |
| 				"type":   *change.PrivateResourceRecord.Type,
 | |
| 				"ttl":    *change.PrivateResourceRecord.TTL,
 | |
| 				"action": change.Action,
 | |
| 			}
 | |
| 
 | |
| 			log.WithFields(logFields).Info("Changing record.")
 | |
| 
 | |
| 			if p.DryRun {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			if change.Action == recordUpdate {
 | |
| 				recordID := p.getPrivateRecordID(records, change.PrivateResourceRecord)
 | |
| 				if recordID == "" {
 | |
| 					log.WithFields(logFields).Errorf("failed to find previous record: %v", change.PrivateResourceRecord)
 | |
| 					continue
 | |
| 				}
 | |
| 				p.updateRecord(ctx, zoneID, recordID, change)
 | |
| 			} else if change.Action == recordDelete {
 | |
| 				recordID := p.getPrivateRecordID(records, change.PrivateResourceRecord)
 | |
| 				if recordID == "" {
 | |
| 					log.WithFields(logFields).Errorf("failed to find previous record: %v", change.PrivateResourceRecord)
 | |
| 					continue
 | |
| 				}
 | |
| 				p.deleteRecord(ctx, zoneID, recordID)
 | |
| 			} else if change.Action == recordCreate {
 | |
| 				p.createRecord(ctx, zoneID, change)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // privateZones return zones in private dns
 | |
| func (p *IBMCloudProvider) privateZones(ctx context.Context) ([]dnssvcsv1.Dnszone, error) {
 | |
| 	result := []dnssvcsv1.Dnszone{}
 | |
| 	// if there is a zoneIDfilter configured
 | |
| 	// && if the filter isn't just a blank string (used in tests)
 | |
| 	if len(p.zoneIDFilter.ZoneIDs) > 0 && p.zoneIDFilter.ZoneIDs[0] != "" {
 | |
| 		log.Debugln("zoneIDFilter configured. only looking up zone IDs defined")
 | |
| 		for _, zoneID := range p.zoneIDFilter.ZoneIDs {
 | |
| 			log.Debugf("looking up zone %s", zoneID)
 | |
| 			detailResponse, _, err := p.Client.GetDnszoneWithContext(ctx, &dnssvcsv1.GetDnszoneOptions{
 | |
| 				InstanceID: core.StringPtr(p.instanceID),
 | |
| 				DnszoneID:  core.StringPtr(zoneID),
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				log.Errorf("zone %s lookup failed, %v", zoneID, err)
 | |
| 				continue
 | |
| 			}
 | |
| 			log.WithFields(log.Fields{
 | |
| 				"zoneName": *detailResponse.Name,
 | |
| 				"zoneID":   *detailResponse.ID,
 | |
| 			}).Debugln("adding zone for consideration")
 | |
| 			result = append(result, *detailResponse)
 | |
| 		}
 | |
| 		return result, nil
 | |
| 	}
 | |
| 
 | |
| 	log.Debugln("no zoneIDFilter configured, looking at all zones")
 | |
| 
 | |
| 	zonesResponse, _, err := p.Client.ListDnszonesWithContext(ctx, &dnssvcsv1.ListDnszonesOptions{
 | |
| 		InstanceID: core.StringPtr(p.instanceID),
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, zone := range zonesResponse.Dnszones {
 | |
| 		if !p.domainFilter.Match(*zone.Name) {
 | |
| 			log.Debugf("zone %s not in domain filter", *zone.Name)
 | |
| 			continue
 | |
| 		}
 | |
| 		result = append(result, zone)
 | |
| 	}
 | |
| 
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| // activePrivateZone active zone with new records add if not active
 | |
| func (p *IBMCloudProvider) activePrivateZone(ctx context.Context, zoneID, vpc string) {
 | |
| 	permittedNetworkVpc := &dnssvcsv1.PermittedNetworkVpc{
 | |
| 		VpcCrn: core.StringPtr(vpc),
 | |
| 	}
 | |
| 	createPermittedNetworkOptions := &dnssvcsv1.CreatePermittedNetworkOptions{
 | |
| 		InstanceID:       core.StringPtr(p.instanceID),
 | |
| 		DnszoneID:        core.StringPtr(zoneID),
 | |
| 		PermittedNetwork: permittedNetworkVpc,
 | |
| 		Type:             core.StringPtr("vpc"),
 | |
| 	}
 | |
| 	_, _, err := p.Client.CreatePermittedNetworkWithContext(ctx, createPermittedNetworkOptions)
 | |
| 	if err != nil {
 | |
| 		log.Errorf("failed to active zone %s in VPC %s with error: %v", zoneID, vpc, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // changesByPrivateZone separates a multi-zone change into a single change per zone.
 | |
| func (p *IBMCloudProvider) changesByPrivateZone(ctx context.Context, zones []dnssvcsv1.Dnszone, changeSet []*ibmcloudChange) map[string][]*ibmcloudChange {
 | |
| 	changes := make(map[string][]*ibmcloudChange)
 | |
| 	zoneNameIDMapper := provider.ZoneIDName{}
 | |
| 	for _, z := range zones {
 | |
| 		zoneNameIDMapper.Add(*z.ID, *z.Name)
 | |
| 		changes[*z.ID] = []*ibmcloudChange{}
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range changeSet {
 | |
| 		zoneID, _ := zoneNameIDMapper.FindZone(*c.PrivateResourceRecord.Name)
 | |
| 		if zoneID == "" {
 | |
| 			log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", *c.PrivateResourceRecord.Name)
 | |
| 			continue
 | |
| 		}
 | |
| 		changes[zoneID] = append(changes[zoneID], c)
 | |
| 	}
 | |
| 
 | |
| 	return changes
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) publicRecords(ctx context.Context) ([]*endpoint.Endpoint, error) {
 | |
| 	log.Debugf("Listing records on public zone")
 | |
| 	dnsRecords, err := p.listAllPublicRecords(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return p.groupPublicRecords(dnsRecords), nil
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) listAllPublicRecords(ctx context.Context) ([]dnsrecordsv1.DnsrecordDetails, error) {
 | |
| 	var dnsRecords []dnsrecordsv1.DnsrecordDetails
 | |
| 	page := 1
 | |
| GETRECORDS:
 | |
| 	listAllDNSRecordsOptions := &dnsrecordsv1.ListAllDnsRecordsOptions{
 | |
| 		Page: core.Int64Ptr(int64(page)),
 | |
| 	}
 | |
| 	records, _, err := p.Client.ListAllDDNSRecordsWithContext(ctx, listAllDNSRecordsOptions)
 | |
| 	if err != nil {
 | |
| 		return dnsRecords, err
 | |
| 	}
 | |
| 	dnsRecords = append(dnsRecords, records.Result...)
 | |
| 	// Loop if more records exist
 | |
| 	if *records.ResultInfo.TotalCount > int64(page*100) {
 | |
| 		page = page + 1
 | |
| 		log.Debugf("More than one pages records found, page: %d", page)
 | |
| 		goto GETRECORDS
 | |
| 	}
 | |
| 	return dnsRecords, nil
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) groupPublicRecords(records []dnsrecordsv1.DnsrecordDetails) []*endpoint.Endpoint {
 | |
| 	endpoints := []*endpoint.Endpoint{}
 | |
| 
 | |
| 	// group supported records by name and type
 | |
| 	groups := map[string][]dnsrecordsv1.DnsrecordDetails{}
 | |
| 
 | |
| 	for _, r := range records {
 | |
| 		if !provider.SupportedRecordType(*r.Type) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		groupBy := *r.Name + *r.Type
 | |
| 		if _, ok := groups[groupBy]; !ok {
 | |
| 			groups[groupBy] = []dnsrecordsv1.DnsrecordDetails{}
 | |
| 		}
 | |
| 
 | |
| 		groups[groupBy] = append(groups[groupBy], r)
 | |
| 	}
 | |
| 
 | |
| 	// create single endpoint with all the targets for each name/type
 | |
| 	for _, records := range groups {
 | |
| 		targets := make([]string, len(records))
 | |
| 		for i, record := range records {
 | |
| 			targets[i] = *record.Content
 | |
| 		}
 | |
| 
 | |
| 		ep := endpoint.NewEndpointWithTTL(
 | |
| 			*records[0].Name,
 | |
| 			*records[0].Type,
 | |
| 			endpoint.TTL(*records[0].TTL),
 | |
| 			targets...).WithProviderSpecific(proxyFilter, strconv.FormatBool(*records[0].Proxied))
 | |
| 
 | |
| 		log.Debugf(
 | |
| 			"Found %s record for '%s' with target '%s'.",
 | |
| 			ep.RecordType,
 | |
| 			ep.DNSName,
 | |
| 			ep.Targets,
 | |
| 		)
 | |
| 
 | |
| 		endpoints = append(endpoints, ep)
 | |
| 	}
 | |
| 	return endpoints
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) privateRecords(ctx context.Context) ([]*endpoint.Endpoint, error) {
 | |
| 	log.Debugf("Listing records on private zone")
 | |
| 	var vpc string
 | |
| 	zones, err := p.privateZones(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	sources, err := p.source.Endpoints(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// Filter VPC annoation for private zone active
 | |
| 	for _, source := range sources {
 | |
| 		vpc = checkVPCAnnotation(source)
 | |
| 		if len(vpc) > 0 {
 | |
| 			log.Debugf("VPC found: %s", vpc)
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	endpoints := []*endpoint.Endpoint{}
 | |
| 	for _, zone := range zones {
 | |
| 		if len(vpc) > 0 && *zone.State == zoneStatePendingNetwork {
 | |
| 			log.Debugf("active zone: %s", *zone.ID)
 | |
| 			p.activePrivateZone(ctx, *zone.ID, vpc)
 | |
| 		}
 | |
| 
 | |
| 		dnsRecords, err := p.listAllPrivateRecords(ctx, *zone.ID)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		endpoints = append(endpoints, p.groupPrivateRecords(dnsRecords)...)
 | |
| 	}
 | |
| 
 | |
| 	return endpoints, nil
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) listAllPrivateRecords(ctx context.Context, zoneID string) ([]dnssvcsv1.ResourceRecord, error) {
 | |
| 	var dnsRecords []dnssvcsv1.ResourceRecord
 | |
| 	offset := 0
 | |
| GETRECORDS:
 | |
| 	listResourceRecordsOptions := &dnssvcsv1.ListResourceRecordsOptions{
 | |
| 		InstanceID: core.StringPtr(p.instanceID),
 | |
| 		DnszoneID:  core.StringPtr(zoneID),
 | |
| 		Offset:     core.Int64Ptr(int64(offset)),
 | |
| 	}
 | |
| 	records, _, err := p.Client.ListResourceRecordsWithContext(ctx, listResourceRecordsOptions)
 | |
| 	if err != nil {
 | |
| 		return dnsRecords, err
 | |
| 	}
 | |
| 	oRecords := records.ResourceRecords
 | |
| 	dnsRecords = append(dnsRecords, oRecords...)
 | |
| 	// Loop if more records exist
 | |
| 	if int64(offset+1) < *records.TotalCount && int64(offset+200) < *records.TotalCount {
 | |
| 		offset = offset + 200
 | |
| 		log.Debugf("More than one pages records found, page: %d", offset/200+1)
 | |
| 		goto GETRECORDS
 | |
| 	}
 | |
| 	return dnsRecords, nil
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) groupPrivateRecords(records []dnssvcsv1.ResourceRecord) []*endpoint.Endpoint {
 | |
| 	endpoints := []*endpoint.Endpoint{}
 | |
| 	// group supported records by name and type
 | |
| 	groups := map[string][]dnssvcsv1.ResourceRecord{}
 | |
| 	for _, r := range records {
 | |
| 		if !provider.SupportedRecordType(*r.Type) || !privateTypeSupported[*r.Type] {
 | |
| 			continue
 | |
| 		}
 | |
| 		rname := *r.Name
 | |
| 		rtype := *r.Type
 | |
| 		groupBy := rname + rtype
 | |
| 		if _, ok := groups[groupBy]; !ok {
 | |
| 			groups[groupBy] = []dnssvcsv1.ResourceRecord{}
 | |
| 		}
 | |
| 
 | |
| 		groups[groupBy] = append(groups[groupBy], r)
 | |
| 	}
 | |
| 
 | |
| 	// create single endpoint with all the targets for each name/type
 | |
| 	for _, records := range groups {
 | |
| 		targets := make([]string, len(records))
 | |
| 		for i, record := range records {
 | |
| 			data := record.Rdata
 | |
| 			log.Debugf("record data: %v", data)
 | |
| 			switch *record.Type {
 | |
| 			case "A":
 | |
| 				if !isNil(data["ip"]) {
 | |
| 					targets[i] = data["ip"].(string)
 | |
| 				}
 | |
| 			case "CNAME":
 | |
| 				if !isNil(data["cname"]) {
 | |
| 					targets[i] = data["cname"].(string)
 | |
| 				}
 | |
| 			case "TXT":
 | |
| 				if !isNil(data["text"]) {
 | |
| 					targets[i] = data["text"].(string)
 | |
| 				}
 | |
| 				log.Debugf("text record data: %v", targets[i])
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		ep := endpoint.NewEndpointWithTTL(
 | |
| 			*records[0].Name,
 | |
| 			*records[0].Type,
 | |
| 			endpoint.TTL(*records[0].TTL), targets...)
 | |
| 
 | |
| 		log.Debugf(
 | |
| 			"Found %s record for '%s' with target '%s'.",
 | |
| 			ep.RecordType,
 | |
| 			ep.DNSName,
 | |
| 			ep.Targets,
 | |
| 		)
 | |
| 
 | |
| 		endpoints = append(endpoints, ep)
 | |
| 	}
 | |
| 	return endpoints
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) getPublicRecordID(records []dnsrecordsv1.DnsrecordDetails, record dnsrecordsv1.DnsrecordDetails) string {
 | |
| 	for _, zoneRecord := range records {
 | |
| 		if *zoneRecord.Name == *record.Name && *zoneRecord.Type == *record.Type && *zoneRecord.Content == *record.Content {
 | |
| 			return *zoneRecord.ID
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) getPrivateRecordID(records []dnssvcsv1.ResourceRecord, record dnssvcsv1.ResourceRecord) string {
 | |
| 	for _, zoneRecord := range records {
 | |
| 		if *zoneRecord.Name == *record.Name && *zoneRecord.Type == *record.Type {
 | |
| 			return *zoneRecord.ID
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) newIBMCloudChange(action string, endpoint *endpoint.Endpoint, target string) *ibmcloudChange {
 | |
| 	ttl := defaultPublicRecordTTL
 | |
| 	proxied := shouldBeProxied(endpoint, p.proxiedByDefault)
 | |
| 
 | |
| 	if endpoint.RecordTTL.IsConfigured() {
 | |
| 		ttl = int(endpoint.RecordTTL)
 | |
| 	}
 | |
| 
 | |
| 	if p.privateZone {
 | |
| 		rData := make(map[string]interface{})
 | |
| 		switch endpoint.RecordType {
 | |
| 		case "A":
 | |
| 			rData[dnssvcsv1.CreateResourceRecordOptions_Type_A] = &dnssvcsv1.ResourceRecordInputRdataRdataARecord{
 | |
| 				Ip: core.StringPtr(target),
 | |
| 			}
 | |
| 		case "CNAME":
 | |
| 			rData[dnssvcsv1.CreateResourceRecordOptions_Type_Cname] = &dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord{
 | |
| 				Cname: core.StringPtr(target),
 | |
| 			}
 | |
| 		case "TXT":
 | |
| 			rData[dnssvcsv1.CreateResourceRecordOptions_Type_Txt] = &dnssvcsv1.ResourceRecordInputRdataRdataTxtRecord{
 | |
| 				Text: core.StringPtr(target),
 | |
| 			}
 | |
| 		}
 | |
| 		return &ibmcloudChange{
 | |
| 			Action: action,
 | |
| 			PrivateResourceRecord: dnssvcsv1.ResourceRecord{
 | |
| 				Name:  core.StringPtr(endpoint.DNSName),
 | |
| 				TTL:   core.Int64Ptr(int64(ttl)),
 | |
| 				Type:  core.StringPtr(endpoint.RecordType),
 | |
| 				Rdata: rData,
 | |
| 			},
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &ibmcloudChange{
 | |
| 		Action: action,
 | |
| 		PublicResourceRecord: dnsrecordsv1.DnsrecordDetails{
 | |
| 			Name:    core.StringPtr(endpoint.DNSName),
 | |
| 			TTL:     core.Int64Ptr(int64(ttl)),
 | |
| 			Proxied: core.BoolPtr(proxied),
 | |
| 			Type:    core.StringPtr(endpoint.RecordType),
 | |
| 			Content: core.StringPtr(target),
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) createRecord(ctx context.Context, zoneID string, change *ibmcloudChange) {
 | |
| 	if p.privateZone {
 | |
| 		createResourceRecordOptions := &dnssvcsv1.CreateResourceRecordOptions{
 | |
| 			InstanceID: core.StringPtr(p.instanceID),
 | |
| 			DnszoneID:  core.StringPtr(zoneID),
 | |
| 			Name:       change.PrivateResourceRecord.Name,
 | |
| 			Type:       change.PrivateResourceRecord.Type,
 | |
| 			TTL:        change.PrivateResourceRecord.TTL,
 | |
| 		}
 | |
| 		switch *change.PrivateResourceRecord.Type {
 | |
| 		case "A":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_A].(*dnssvcsv1.ResourceRecordInputRdataRdataARecord)
 | |
| 			aData, _ := p.Client.NewResourceRecordInputRdataRdataARecord(*data.Ip)
 | |
| 			createResourceRecordOptions.SetRdata(aData)
 | |
| 		case "CNAME":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_Cname].(*dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord)
 | |
| 			cnameData, _ := p.Client.NewResourceRecordInputRdataRdataCnameRecord(*data.Cname)
 | |
| 			createResourceRecordOptions.SetRdata(cnameData)
 | |
| 		case "TXT":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_Txt].(*dnssvcsv1.ResourceRecordInputRdataRdataTxtRecord)
 | |
| 			txtData, _ := p.Client.NewResourceRecordInputRdataRdataTxtRecord(*data.Text)
 | |
| 			createResourceRecordOptions.SetRdata(txtData)
 | |
| 		}
 | |
| 		_, _, err := p.Client.CreateResourceRecordWithContext(ctx, createResourceRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to create %s type record named %s: %v", *change.PrivateResourceRecord.Type, *change.PrivateResourceRecord.Name, err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		createDNSRecordOptions := &dnsrecordsv1.CreateDnsRecordOptions{
 | |
| 			Name:    change.PublicResourceRecord.Name,
 | |
| 			Type:    change.PublicResourceRecord.Type,
 | |
| 			TTL:     change.PublicResourceRecord.TTL,
 | |
| 			Content: change.PublicResourceRecord.Content,
 | |
| 		}
 | |
| 		_, _, err := p.Client.CreateDNSRecordWithContext(ctx, createDNSRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to create %s type record named %s: %v", *change.PublicResourceRecord.Type, *change.PublicResourceRecord.Name, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) updateRecord(ctx context.Context, zoneID, recordID string, change *ibmcloudChange) {
 | |
| 	if p.privateZone {
 | |
| 		updateResourceRecordOptions := &dnssvcsv1.UpdateResourceRecordOptions{
 | |
| 			InstanceID: core.StringPtr(p.instanceID),
 | |
| 			DnszoneID:  core.StringPtr(zoneID),
 | |
| 			RecordID:   core.StringPtr(recordID),
 | |
| 			Name:       change.PrivateResourceRecord.Name,
 | |
| 			TTL:        change.PrivateResourceRecord.TTL,
 | |
| 		}
 | |
| 		switch *change.PrivateResourceRecord.Type {
 | |
| 		case "A":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_A].(*dnssvcsv1.ResourceRecordInputRdataRdataARecord)
 | |
| 			aData, _ := p.Client.NewResourceRecordUpdateInputRdataRdataARecord(*data.Ip)
 | |
| 			updateResourceRecordOptions.SetRdata(aData)
 | |
| 		case "CNAME":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_Cname].(*dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord)
 | |
| 			cnameData, _ := p.Client.NewResourceRecordUpdateInputRdataRdataCnameRecord(*data.Cname)
 | |
| 			updateResourceRecordOptions.SetRdata(cnameData)
 | |
| 		case "TXT":
 | |
| 			data, _ := change.PrivateResourceRecord.Rdata[dnssvcsv1.CreateResourceRecordOptions_Type_Txt].(*dnssvcsv1.ResourceRecordInputRdataRdataTxtRecord)
 | |
| 			txtData, _ := p.Client.NewResourceRecordUpdateInputRdataRdataTxtRecord(*data.Text)
 | |
| 			updateResourceRecordOptions.SetRdata(txtData)
 | |
| 		}
 | |
| 		_, _, err := p.Client.UpdateResourceRecordWithContext(ctx, updateResourceRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to update %s type record named %s: %v", *change.PublicResourceRecord.Type, *change.PublicResourceRecord.Name, err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		updateDNSRecordOptions := &dnsrecordsv1.UpdateDnsRecordOptions{
 | |
| 			DnsrecordIdentifier: &recordID,
 | |
| 			Name:                change.PublicResourceRecord.Name,
 | |
| 			Type:                change.PublicResourceRecord.Type,
 | |
| 			TTL:                 change.PublicResourceRecord.TTL,
 | |
| 			Content:             change.PublicResourceRecord.Content,
 | |
| 			Proxied:             change.PublicResourceRecord.Proxied,
 | |
| 		}
 | |
| 		_, _, err := p.Client.UpdateDNSRecordWithContext(ctx, updateDNSRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to update %s type record named %s: %v", *change.PublicResourceRecord.Type, *change.PublicResourceRecord.Name, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *IBMCloudProvider) deleteRecord(ctx context.Context, zoneID, recordID string) {
 | |
| 	if p.privateZone {
 | |
| 		deleteResourceRecordOptions := &dnssvcsv1.DeleteResourceRecordOptions{
 | |
| 			InstanceID: core.StringPtr(p.instanceID),
 | |
| 			DnszoneID:  core.StringPtr(zoneID),
 | |
| 			RecordID:   core.StringPtr(recordID),
 | |
| 		}
 | |
| 		_, err := p.Client.DeleteResourceRecordWithContext(ctx, deleteResourceRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to delete record %s: %v", recordID, err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		deleteDNSRecordOptions := &dnsrecordsv1.DeleteDnsRecordOptions{
 | |
| 			DnsrecordIdentifier: &recordID,
 | |
| 		}
 | |
| 		_, _, err := p.Client.DeleteDNSRecordWithContext(ctx, deleteDNSRecordOptions)
 | |
| 		if err != nil {
 | |
| 			log.Errorf("failed to delete record %s: %v", recordID, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func shouldBeProxied(endpoint *endpoint.Endpoint, proxiedByDefault bool) bool {
 | |
| 	proxied := proxiedByDefault
 | |
| 
 | |
| 	for _, v := range endpoint.ProviderSpecific {
 | |
| 		if v.Name == proxyFilter {
 | |
| 			b, err := strconv.ParseBool(v.Value)
 | |
| 			if err != nil {
 | |
| 				log.Errorf("Failed to parse annotation [%s]: %v", proxyFilter, err)
 | |
| 			} else {
 | |
| 				proxied = b
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if proxyTypeNotSupported[endpoint.RecordType] || strings.Contains(endpoint.DNSName, "*") {
 | |
| 		proxied = false
 | |
| 	}
 | |
| 	return proxied
 | |
| }
 | |
| 
 | |
| func checkVPCAnnotation(endpoint *endpoint.Endpoint) string {
 | |
| 	var vpc string
 | |
| 	for _, v := range endpoint.ProviderSpecific {
 | |
| 		if v.Name == vpcFilter {
 | |
| 			vpcCrn, err := crn.Parse(v.Value)
 | |
| 			if vpcCrn.ResourceType != "vpc" || err != nil {
 | |
| 				log.Errorf("Failed to parse vpc [%s]: %v", v.Value, err)
 | |
| 			} else {
 | |
| 				vpc = v.Value
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return vpc
 | |
| }
 | |
| 
 | |
| func isNil(i interface{}) bool {
 | |
| 	if i == nil {
 | |
| 		return true
 | |
| 	}
 | |
| 	switch reflect.TypeOf(i).Kind() {
 | |
| 	case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
 | |
| 		return reflect.ValueOf(i).IsNil()
 | |
| 	}
 | |
| 	return false
 | |
| }
 |