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

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  package ultradns
    15  
    16  import (
    17  	"context"
    18  	"encoding/base64"
    19  	"fmt"
    20  	"os"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	log "github.com/sirupsen/logrus"
    26  	udnssdk "github.com/ultradns/ultradns-sdk-go"
    27  
    28  	"sigs.k8s.io/external-dns/endpoint"
    29  	"sigs.k8s.io/external-dns/plan"
    30  	"sigs.k8s.io/external-dns/provider"
    31  )
    32  
    33  const (
    34  	ultradnsCreate = "CREATE"
    35  	ultradnsDelete = "DELETE"
    36  	ultradnsUpdate = "UPDATE"
    37  	sbPoolPriority = 1
    38  	sbPoolOrder    = "ROUND_ROBIN"
    39  	rdPoolOrder    = "ROUND_ROBIN"
    40  )
    41  
    42  // global variables
    43  var sbPoolRunProbes = true
    44  
    45  var (
    46  	sbPoolActOnProbes = true
    47  	ultradnsPoolType  = "rdpool"
    48  	accountName       string
    49  )
    50  
    51  // Setting custom headers for ultradns api calls
    52  var customHeader = []udnssdk.CustomHeader{
    53  	{
    54  		Key:   "UltraClient",
    55  		Value: "kube-client",
    56  	},
    57  }
    58  
    59  // UltraDNSProvider struct
    60  type UltraDNSProvider struct {
    61  	provider.BaseProvider
    62  	client       udnssdk.Client
    63  	domainFilter endpoint.DomainFilter
    64  	dryRun       bool
    65  }
    66  
    67  // UltraDNSChanges struct
    68  type UltraDNSChanges struct {
    69  	Action                    string
    70  	ResourceRecordSetUltraDNS udnssdk.RRSet
    71  }
    72  
    73  // NewUltraDNSProvider initializes a new UltraDNS DNS based provider
    74  func NewUltraDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*UltraDNSProvider, error) {
    75  	username, ok := os.LookupEnv("ULTRADNS_USERNAME")
    76  	udnssdk.SetCustomHeader = customHeader
    77  	if !ok {
    78  		return nil, fmt.Errorf("no username found")
    79  	}
    80  
    81  	base64password, ok := os.LookupEnv("ULTRADNS_PASSWORD")
    82  	if !ok {
    83  		return nil, fmt.Errorf("no password found")
    84  	}
    85  
    86  	// Base64 Standard Decoding
    87  	password, err := base64.StdEncoding.DecodeString(base64password)
    88  	if err != nil {
    89  		fmt.Printf("Error decoding string: %s ", err.Error())
    90  		return nil, err
    91  	}
    92  
    93  	baseURL, ok := os.LookupEnv("ULTRADNS_BASEURL")
    94  	if !ok {
    95  		return nil, fmt.Errorf("no baseurl found")
    96  	}
    97  	accountName, ok = os.LookupEnv("ULTRADNS_ACCOUNTNAME")
    98  	if !ok {
    99  		accountName = ""
   100  	}
   101  
   102  	probeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_PROBING")
   103  	if ok {
   104  		if (probeValue != "true") && (probeValue != "false") {
   105  			return nil, fmt.Errorf("please set proper probe value, the values can be either true or false")
   106  		}
   107  		sbPoolRunProbes, _ = strconv.ParseBool(probeValue)
   108  	}
   109  
   110  	actOnProbeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_ACTONPROBE")
   111  	if ok {
   112  		if (actOnProbeValue != "true") && (actOnProbeValue != "false") {
   113  			return nil, fmt.Errorf("please set proper act on probe value, the values can be either true or false")
   114  		}
   115  		sbPoolActOnProbes, _ = strconv.ParseBool(actOnProbeValue)
   116  	}
   117  
   118  	poolValue, ok := os.LookupEnv("ULTRADNS_POOL_TYPE")
   119  	if ok {
   120  		if (poolValue != "sbpool") && (poolValue != "rdpool") {
   121  			return nil, fmt.Errorf(" please set proper ULTRADNS_POOL_TYPE, supported types are sbpool or rdpool")
   122  		}
   123  		ultradnsPoolType = poolValue
   124  	}
   125  
   126  	client, err := udnssdk.NewClient(username, string(password), baseURL)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("connection cannot be established")
   129  	}
   130  
   131  	provider := &UltraDNSProvider{
   132  		client:       *client,
   133  		domainFilter: domainFilter,
   134  		dryRun:       dryRun,
   135  	}
   136  
   137  	return provider, nil
   138  }
   139  
   140  // Zones returns list of hosted zones
   141  func (p *UltraDNSProvider) Zones(ctx context.Context) ([]udnssdk.Zone, error) {
   142  	zoneKey := &udnssdk.ZoneKey{}
   143  	var err error
   144  
   145  	if p.domainFilter.IsConfigured() {
   146  		zonesAppender := []udnssdk.Zone{}
   147  		for _, zone := range p.domainFilter.Filters {
   148  			zoneKey.Zone = zone
   149  			zoneKey.AccountName = accountName
   150  			zones, err := p.fetchZones(ctx, zoneKey)
   151  			if err != nil {
   152  				return nil, err
   153  			}
   154  
   155  			zonesAppender = append(zonesAppender, zones...)
   156  		}
   157  		return zonesAppender, nil
   158  	}
   159  	zoneKey.AccountName = accountName
   160  	zones, err := p.fetchZones(ctx, zoneKey)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	return zones, nil
   166  }
   167  
   168  func (p *UltraDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
   169  	var endpoints []*endpoint.Endpoint
   170  
   171  	zones, err := p.Zones(ctx)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	for _, zone := range zones {
   177  		log.Infof("zones : %v", zone)
   178  		var rrsetType string
   179  		var ownerName string
   180  		rrsetKey := udnssdk.RRSetKey{
   181  			Zone: zone.Properties.Name,
   182  			Type: rrsetType,
   183  			Name: ownerName,
   184  		}
   185  
   186  		if zone.Properties.ResourceRecordCount != 0 {
   187  			records, err := p.fetchRecords(ctx, rrsetKey)
   188  			if err != nil {
   189  				return nil, err
   190  			}
   191  
   192  			for _, r := range records {
   193  				recordTypeArray := strings.Fields(r.RRType)
   194  				if provider.SupportedRecordType(recordTypeArray[0]) {
   195  					log.Infof("owner name %s", r.OwnerName)
   196  					name := r.OwnerName
   197  
   198  					// root name is identified by the empty string and should be
   199  					// translated to zone name for the endpoint entry.
   200  					if r.OwnerName == "" {
   201  						name = zone.Properties.Name
   202  					}
   203  
   204  					endPointTTL := endpoint.NewEndpointWithTTL(name, recordTypeArray[0], endpoint.TTL(r.TTL), r.RData...)
   205  					endpoints = append(endpoints, endPointTTL)
   206  				}
   207  			}
   208  		}
   209  	}
   210  	log.Infof("endpoints %v", endpoints)
   211  	return endpoints, nil
   212  }
   213  
   214  func (p *UltraDNSProvider) fetchRecords(ctx context.Context, k udnssdk.RRSetKey) ([]udnssdk.RRSet, error) {
   215  	// Logic to paginate through all available results
   216  	maxerrs := 5
   217  	waittime := 5 * time.Second
   218  
   219  	rrsets := []udnssdk.RRSet{}
   220  	errcnt := 0
   221  	offset := 0
   222  	limit := 1000
   223  
   224  	for {
   225  		reqRrsets, ri, res, err := p.client.RRSets.SelectWithOffsetWithLimit(k, offset, limit)
   226  		if err != nil {
   227  			if res != nil && res.StatusCode >= 500 {
   228  				errcnt = errcnt + 1
   229  				if errcnt < maxerrs {
   230  					time.Sleep(waittime)
   231  					continue
   232  				}
   233  			}
   234  			return rrsets, err
   235  		}
   236  		rrsets = append(rrsets, reqRrsets...)
   237  
   238  		if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
   239  			return rrsets, nil
   240  		}
   241  		offset = ri.ReturnedCount + ri.Offset
   242  		continue
   243  	}
   244  }
   245  
   246  func (p *UltraDNSProvider) fetchZones(ctx context.Context, zoneKey *udnssdk.ZoneKey) ([]udnssdk.Zone, error) {
   247  	// Logic to paginate through all available results
   248  	offset := 0
   249  	limit := 1000
   250  	maxerrs := 5
   251  	waittime := 5 * time.Second
   252  
   253  	zones := []udnssdk.Zone{}
   254  
   255  	errcnt := 0
   256  
   257  	for {
   258  		reqZones, ri, res, err := p.client.Zone.SelectWithOffsetWithLimit(zoneKey, offset, limit)
   259  		if err != nil {
   260  			if res != nil && res.StatusCode >= 500 {
   261  				errcnt = errcnt + 1
   262  				if errcnt < maxerrs {
   263  					time.Sleep(waittime)
   264  					continue
   265  				}
   266  			}
   267  			return zones, err
   268  		}
   269  
   270  		zones = append(zones, reqZones...)
   271  		if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
   272  			return zones, nil
   273  		}
   274  		offset = ri.ReturnedCount + ri.Offset
   275  		continue
   276  	}
   277  }
   278  
   279  func (p *UltraDNSProvider) submitChanges(ctx context.Context, changes []*UltraDNSChanges) error {
   280  	cnameownerName := "cname"
   281  	txtownerName := "txt"
   282  	if len(changes) == 0 {
   283  		log.Infof("All records are already up to date")
   284  		return nil
   285  	}
   286  
   287  	zones, err := p.Zones(ctx)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	zoneChanges := seperateChangeByZone(zones, changes)
   292  
   293  	for zoneName, changes := range zoneChanges {
   294  		for _, change := range changes {
   295  			if change.ResourceRecordSetUltraDNS.RRType == "CNAME" {
   296  				cnameownerName = change.ResourceRecordSetUltraDNS.OwnerName
   297  			} else if change.ResourceRecordSetUltraDNS.RRType == "TXT" {
   298  				txtownerName = change.ResourceRecordSetUltraDNS.OwnerName
   299  			}
   300  
   301  			if cnameownerName == txtownerName {
   302  				rrsetKey := udnssdk.RRSetKey{
   303  					Zone: zoneName,
   304  					Type: endpoint.RecordTypeCNAME,
   305  					Name: change.ResourceRecordSetUltraDNS.OwnerName,
   306  				}
   307  				err := p.getSpecificRecord(ctx, rrsetKey)
   308  				if err != nil {
   309  					return err
   310  				}
   311  				if !p.dryRun {
   312  					_, err = p.client.RRSets.Delete(rrsetKey)
   313  					if err != nil {
   314  						return err
   315  					}
   316  				}
   317  				return fmt.Errorf("the 'cname' and 'txt' record name cannot be same please recreate external-dns with - --txt-prefix=")
   318  			}
   319  			rrsetKey := udnssdk.RRSetKey{
   320  				Zone: zoneName,
   321  				Type: change.ResourceRecordSetUltraDNS.RRType,
   322  				Name: change.ResourceRecordSetUltraDNS.OwnerName,
   323  			}
   324  			record := udnssdk.RRSet{}
   325  			if (change.ResourceRecordSetUltraDNS.RRType == "A" || change.ResourceRecordSetUltraDNS.RRType == "AAAA") && (len(change.ResourceRecordSetUltraDNS.RData) >= 2) {
   326  				if ultradnsPoolType == "sbpool" && change.ResourceRecordSetUltraDNS.RRType == "A" {
   327  					sbPoolObject, _ := p.newSBPoolObjectCreation(ctx, change)
   328  					record = udnssdk.RRSet{
   329  						RRType:    change.ResourceRecordSetUltraDNS.RRType,
   330  						OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
   331  						RData:     change.ResourceRecordSetUltraDNS.RData,
   332  						TTL:       change.ResourceRecordSetUltraDNS.TTL,
   333  						Profile:   sbPoolObject.RawProfile(),
   334  					}
   335  				} else if ultradnsPoolType == "rdpool" {
   336  					rdPoolObject, _ := p.newRDPoolObjectCreation(ctx, change)
   337  					record = udnssdk.RRSet{
   338  						RRType:    change.ResourceRecordSetUltraDNS.RRType,
   339  						OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
   340  						RData:     change.ResourceRecordSetUltraDNS.RData,
   341  						TTL:       change.ResourceRecordSetUltraDNS.TTL,
   342  						Profile:   rdPoolObject.RawProfile(),
   343  					}
   344  				} else {
   345  					return fmt.Errorf("we do not support Multiple target 'aaaa' records in sb pool please contact to neustar for further details")
   346  				}
   347  			} else {
   348  				record = udnssdk.RRSet{
   349  					RRType:    change.ResourceRecordSetUltraDNS.RRType,
   350  					OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
   351  					RData:     change.ResourceRecordSetUltraDNS.RData,
   352  					TTL:       change.ResourceRecordSetUltraDNS.TTL,
   353  				}
   354  			}
   355  
   356  			log.WithFields(log.Fields{
   357  				"record":  record.OwnerName,
   358  				"type":    record.RRType,
   359  				"ttl":     record.TTL,
   360  				"action":  change.Action,
   361  				"zone":    zoneName,
   362  				"profile": record.Profile,
   363  			}).Info("Changing record.")
   364  
   365  			switch change.Action {
   366  			case ultradnsCreate:
   367  				if !p.dryRun {
   368  					res, err := p.client.RRSets.Create(rrsetKey, record)
   369  					_ = res
   370  					if err != nil {
   371  						return err
   372  					}
   373  				}
   374  
   375  			case ultradnsDelete:
   376  				err := p.getSpecificRecord(ctx, rrsetKey)
   377  				if err != nil {
   378  					return err
   379  				}
   380  
   381  				if !p.dryRun {
   382  					_, err = p.client.RRSets.Delete(rrsetKey)
   383  					if err != nil {
   384  						return err
   385  					}
   386  				}
   387  			case ultradnsUpdate:
   388  				err := p.getSpecificRecord(ctx, rrsetKey)
   389  				if err != nil {
   390  					return err
   391  				}
   392  
   393  				if !p.dryRun {
   394  					_, err = p.client.RRSets.Update(rrsetKey, record)
   395  					if err != nil {
   396  						return err
   397  					}
   398  				}
   399  			}
   400  		}
   401  	}
   402  
   403  	return nil
   404  }
   405  
   406  func (p *UltraDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
   407  	combinedChanges := make([]*UltraDNSChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
   408  	log.Infof("value of changes %v,%v,%v", changes.Create, changes.UpdateNew, changes.Delete)
   409  	combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsCreate, changes.Create)...)
   410  	combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsUpdate, changes.UpdateNew)...)
   411  	combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsDelete, changes.Delete)...)
   412  
   413  	return p.submitChanges(ctx, combinedChanges)
   414  }
   415  
   416  func newUltraDNSChanges(action string, endpoints []*endpoint.Endpoint) []*UltraDNSChanges {
   417  	changes := make([]*UltraDNSChanges, 0, len(endpoints))
   418  	var ttl int
   419  	for _, e := range endpoints {
   420  		if e.RecordTTL.IsConfigured() {
   421  			ttl = int(e.RecordTTL)
   422  		}
   423  
   424  		// Adding suffix dot to the record name
   425  		recordName := fmt.Sprintf("%s.", e.DNSName)
   426  		change := &UltraDNSChanges{
   427  			Action: action,
   428  			ResourceRecordSetUltraDNS: udnssdk.RRSet{
   429  				RRType:    e.RecordType,
   430  				OwnerName: recordName,
   431  				RData:     e.Targets,
   432  				TTL:       ttl,
   433  			},
   434  		}
   435  		changes = append(changes, change)
   436  	}
   437  	return changes
   438  }
   439  
   440  func seperateChangeByZone(zones []udnssdk.Zone, changes []*UltraDNSChanges) map[string][]*UltraDNSChanges {
   441  	change := make(map[string][]*UltraDNSChanges)
   442  	zoneNameID := provider.ZoneIDName{}
   443  	for _, z := range zones {
   444  		zoneNameID.Add(z.Properties.Name, z.Properties.Name)
   445  		change[z.Properties.Name] = []*UltraDNSChanges{}
   446  	}
   447  
   448  	for _, c := range changes {
   449  		zone, _ := zoneNameID.FindZone(c.ResourceRecordSetUltraDNS.OwnerName)
   450  		if zone == "" {
   451  			log.Infof("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSetUltraDNS.OwnerName)
   452  			continue
   453  		}
   454  		change[zone] = append(change[zone], c)
   455  	}
   456  	return change
   457  }
   458  
   459  func (p *UltraDNSProvider) getSpecificRecord(ctx context.Context, rrsetKey udnssdk.RRSetKey) (err error) {
   460  	_, err = p.client.RRSets.Select(rrsetKey)
   461  	if err != nil {
   462  		return fmt.Errorf("no record was found for %v", rrsetKey)
   463  	}
   464  
   465  	return nil
   466  }
   467  
   468  // Creation of SBPoolObject
   469  func (p *UltraDNSProvider) newSBPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (sbPool udnssdk.SBPoolProfile, err error) {
   470  	sbpoolRDataList := []udnssdk.SBRDataInfo{}
   471  	for range change.ResourceRecordSetUltraDNS.RData {
   472  		rrdataInfo := udnssdk.SBRDataInfo{
   473  			RunProbes: sbPoolRunProbes,
   474  			Priority:  sbPoolPriority,
   475  			State:     "NORMAL",
   476  			Threshold: 1,
   477  			Weight:    nil,
   478  		}
   479  		sbpoolRDataList = append(sbpoolRDataList, rrdataInfo)
   480  	}
   481  	sbPoolObject := udnssdk.SBPoolProfile{
   482  		Context:     udnssdk.SBPoolSchema,
   483  		Order:       sbPoolOrder,
   484  		Description: change.ResourceRecordSetUltraDNS.OwnerName,
   485  		MaxActive:   len(change.ResourceRecordSetUltraDNS.RData),
   486  		MaxServed:   len(change.ResourceRecordSetUltraDNS.RData),
   487  		RDataInfo:   sbpoolRDataList,
   488  		RunProbes:   sbPoolRunProbes,
   489  		ActOnProbes: sbPoolActOnProbes,
   490  	}
   491  	return sbPoolObject, nil
   492  }
   493  
   494  // Creation of RDPoolObject
   495  func (p *UltraDNSProvider) newRDPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (rdPool udnssdk.RDPoolProfile, err error) {
   496  	rdPoolObject := udnssdk.RDPoolProfile{
   497  		Context:     udnssdk.RDPoolSchema,
   498  		Order:       rdPoolOrder,
   499  		Description: change.ResourceRecordSetUltraDNS.OwnerName,
   500  	}
   501  	return rdPoolObject, nil
   502  }