mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
503 lines
14 KiB
Go
503 lines
14 KiB
Go
/*
|
|
Copyright 2020 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 ultradns
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
udnssdk "github.com/ultradns/ultradns-sdk-go"
|
|
|
|
"sigs.k8s.io/external-dns/endpoint"
|
|
"sigs.k8s.io/external-dns/plan"
|
|
"sigs.k8s.io/external-dns/provider"
|
|
)
|
|
|
|
const (
|
|
ultradnsCreate = "CREATE"
|
|
ultradnsDelete = "DELETE"
|
|
ultradnsUpdate = "UPDATE"
|
|
sbPoolPriority = 1
|
|
sbPoolOrder = "ROUND_ROBIN"
|
|
rdPoolOrder = "ROUND_ROBIN"
|
|
)
|
|
|
|
// global variables
|
|
var sbPoolRunProbes = true
|
|
|
|
var (
|
|
sbPoolActOnProbes = true
|
|
ultradnsPoolType = "rdpool"
|
|
accountName string
|
|
)
|
|
|
|
// Setting custom headers for ultradns api calls
|
|
var customHeader = []udnssdk.CustomHeader{
|
|
{
|
|
Key: "UltraClient",
|
|
Value: "kube-client",
|
|
},
|
|
}
|
|
|
|
// UltraDNSProvider struct
|
|
type UltraDNSProvider struct {
|
|
provider.BaseProvider
|
|
client udnssdk.Client
|
|
domainFilter endpoint.DomainFilter
|
|
dryRun bool
|
|
}
|
|
|
|
// UltraDNSChanges struct
|
|
type UltraDNSChanges struct {
|
|
Action string
|
|
ResourceRecordSetUltraDNS udnssdk.RRSet
|
|
}
|
|
|
|
// NewUltraDNSProvider initializes a new UltraDNS DNS based provider
|
|
func NewUltraDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*UltraDNSProvider, error) {
|
|
username, ok := os.LookupEnv("ULTRADNS_USERNAME")
|
|
udnssdk.SetCustomHeader = customHeader
|
|
if !ok {
|
|
return nil, fmt.Errorf("no username found")
|
|
}
|
|
|
|
base64password, ok := os.LookupEnv("ULTRADNS_PASSWORD")
|
|
if !ok {
|
|
return nil, fmt.Errorf("no password found")
|
|
}
|
|
|
|
// Base64 Standard Decoding
|
|
password, err := base64.StdEncoding.DecodeString(base64password)
|
|
if err != nil {
|
|
fmt.Printf("Error decoding string: %s ", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
baseURL, ok := os.LookupEnv("ULTRADNS_BASEURL")
|
|
if !ok {
|
|
return nil, fmt.Errorf("no baseurl found")
|
|
}
|
|
accountName, ok = os.LookupEnv("ULTRADNS_ACCOUNTNAME")
|
|
if !ok {
|
|
accountName = ""
|
|
}
|
|
|
|
probeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_PROBING")
|
|
if ok {
|
|
if (probeValue != "true") && (probeValue != "false") {
|
|
return nil, fmt.Errorf("please set proper probe value, the values can be either true or false")
|
|
}
|
|
sbPoolRunProbes, _ = strconv.ParseBool(probeValue)
|
|
}
|
|
|
|
actOnProbeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_ACTONPROBE")
|
|
if ok {
|
|
if (actOnProbeValue != "true") && (actOnProbeValue != "false") {
|
|
return nil, fmt.Errorf("please set proper act on probe value, the values can be either true or false")
|
|
}
|
|
sbPoolActOnProbes, _ = strconv.ParseBool(actOnProbeValue)
|
|
}
|
|
|
|
poolValue, ok := os.LookupEnv("ULTRADNS_POOL_TYPE")
|
|
if ok {
|
|
if (poolValue != "sbpool") && (poolValue != "rdpool") {
|
|
return nil, fmt.Errorf(" please set proper ULTRADNS_POOL_TYPE, supported types are sbpool or rdpool")
|
|
}
|
|
ultradnsPoolType = poolValue
|
|
}
|
|
|
|
client, err := udnssdk.NewClient(username, string(password), baseURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("connection cannot be established")
|
|
}
|
|
|
|
provider := &UltraDNSProvider{
|
|
client: *client,
|
|
domainFilter: domainFilter,
|
|
dryRun: dryRun,
|
|
}
|
|
|
|
return provider, nil
|
|
}
|
|
|
|
// Zones returns list of hosted zones
|
|
func (p *UltraDNSProvider) Zones(ctx context.Context) ([]udnssdk.Zone, error) {
|
|
zoneKey := &udnssdk.ZoneKey{}
|
|
var err error
|
|
|
|
if p.domainFilter.IsConfigured() {
|
|
zonesAppender := []udnssdk.Zone{}
|
|
for _, zone := range p.domainFilter.Filters {
|
|
zoneKey.Zone = zone
|
|
zoneKey.AccountName = accountName
|
|
zones, err := p.fetchZones(ctx, zoneKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
zonesAppender = append(zonesAppender, zones...)
|
|
}
|
|
return zonesAppender, nil
|
|
}
|
|
zoneKey.AccountName = accountName
|
|
zones, err := p.fetchZones(ctx, zoneKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return zones, nil
|
|
}
|
|
|
|
func (p *UltraDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
|
var endpoints []*endpoint.Endpoint
|
|
|
|
zones, err := p.Zones(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, zone := range zones {
|
|
log.Infof("zones : %v", zone)
|
|
var rrsetType string
|
|
var ownerName string
|
|
rrsetKey := udnssdk.RRSetKey{
|
|
Zone: zone.Properties.Name,
|
|
Type: rrsetType,
|
|
Name: ownerName,
|
|
}
|
|
|
|
if zone.Properties.ResourceRecordCount != 0 {
|
|
records, err := p.fetchRecords(ctx, rrsetKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, r := range records {
|
|
recordTypeArray := strings.Fields(r.RRType)
|
|
if provider.SupportedRecordType(recordTypeArray[0]) {
|
|
log.Infof("owner name %s", r.OwnerName)
|
|
name := r.OwnerName
|
|
|
|
// root name is identified by the empty string and should be
|
|
// translated to zone name for the endpoint entry.
|
|
if r.OwnerName == "" {
|
|
name = zone.Properties.Name
|
|
}
|
|
|
|
endPointTTL := endpoint.NewEndpointWithTTL(name, recordTypeArray[0], endpoint.TTL(r.TTL), r.RData...)
|
|
endpoints = append(endpoints, endPointTTL)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
log.Infof("endpoints %v", endpoints)
|
|
return endpoints, nil
|
|
}
|
|
|
|
func (p *UltraDNSProvider) fetchRecords(ctx context.Context, k udnssdk.RRSetKey) ([]udnssdk.RRSet, error) {
|
|
// Logic to paginate through all available results
|
|
maxerrs := 5
|
|
waittime := 5 * time.Second
|
|
|
|
rrsets := []udnssdk.RRSet{}
|
|
errcnt := 0
|
|
offset := 0
|
|
limit := 1000
|
|
|
|
for {
|
|
reqRrsets, ri, res, err := p.client.RRSets.SelectWithOffsetWithLimit(k, offset, limit)
|
|
if err != nil {
|
|
if res != nil && res.StatusCode >= 500 {
|
|
errcnt = errcnt + 1
|
|
if errcnt < maxerrs {
|
|
time.Sleep(waittime)
|
|
continue
|
|
}
|
|
}
|
|
return rrsets, err
|
|
}
|
|
rrsets = append(rrsets, reqRrsets...)
|
|
|
|
if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
|
|
return rrsets, nil
|
|
}
|
|
offset = ri.ReturnedCount + ri.Offset
|
|
continue
|
|
}
|
|
}
|
|
|
|
func (p *UltraDNSProvider) fetchZones(ctx context.Context, zoneKey *udnssdk.ZoneKey) ([]udnssdk.Zone, error) {
|
|
// Logic to paginate through all available results
|
|
offset := 0
|
|
limit := 1000
|
|
maxerrs := 5
|
|
waittime := 5 * time.Second
|
|
|
|
zones := []udnssdk.Zone{}
|
|
|
|
errcnt := 0
|
|
|
|
for {
|
|
reqZones, ri, res, err := p.client.Zone.SelectWithOffsetWithLimit(zoneKey, offset, limit)
|
|
if err != nil {
|
|
if res != nil && res.StatusCode >= 500 {
|
|
errcnt = errcnt + 1
|
|
if errcnt < maxerrs {
|
|
time.Sleep(waittime)
|
|
continue
|
|
}
|
|
}
|
|
return zones, err
|
|
}
|
|
|
|
zones = append(zones, reqZones...)
|
|
if ri.ReturnedCount+ri.Offset >= ri.TotalCount {
|
|
return zones, nil
|
|
}
|
|
offset = ri.ReturnedCount + ri.Offset
|
|
continue
|
|
}
|
|
}
|
|
|
|
func (p *UltraDNSProvider) submitChanges(ctx context.Context, changes []*UltraDNSChanges) error {
|
|
cnameownerName := "cname"
|
|
txtownerName := "txt"
|
|
if len(changes) == 0 {
|
|
log.Infof("All records are already up to date")
|
|
return nil
|
|
}
|
|
|
|
zones, err := p.Zones(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
zoneChanges := seperateChangeByZone(zones, changes)
|
|
|
|
for zoneName, changes := range zoneChanges {
|
|
for _, change := range changes {
|
|
if change.ResourceRecordSetUltraDNS.RRType == "CNAME" {
|
|
cnameownerName = change.ResourceRecordSetUltraDNS.OwnerName
|
|
} else if change.ResourceRecordSetUltraDNS.RRType == "TXT" {
|
|
txtownerName = change.ResourceRecordSetUltraDNS.OwnerName
|
|
}
|
|
|
|
if cnameownerName == txtownerName {
|
|
rrsetKey := udnssdk.RRSetKey{
|
|
Zone: zoneName,
|
|
Type: endpoint.RecordTypeCNAME,
|
|
Name: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
}
|
|
err := p.getSpecificRecord(ctx, rrsetKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.dryRun {
|
|
_, err = p.client.RRSets.Delete(rrsetKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fmt.Errorf("the 'cname' and 'txt' record name cannot be same please recreate external-dns with - --txt-prefix=")
|
|
}
|
|
rrsetKey := udnssdk.RRSetKey{
|
|
Zone: zoneName,
|
|
Type: change.ResourceRecordSetUltraDNS.RRType,
|
|
Name: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
}
|
|
record := udnssdk.RRSet{}
|
|
if (change.ResourceRecordSetUltraDNS.RRType == "A" || change.ResourceRecordSetUltraDNS.RRType == "AAAA") && (len(change.ResourceRecordSetUltraDNS.RData) >= 2) {
|
|
if ultradnsPoolType == "sbpool" && change.ResourceRecordSetUltraDNS.RRType == "A" {
|
|
sbPoolObject, _ := p.newSBPoolObjectCreation(ctx, change)
|
|
record = udnssdk.RRSet{
|
|
RRType: change.ResourceRecordSetUltraDNS.RRType,
|
|
OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
RData: change.ResourceRecordSetUltraDNS.RData,
|
|
TTL: change.ResourceRecordSetUltraDNS.TTL,
|
|
Profile: sbPoolObject.RawProfile(),
|
|
}
|
|
} else if ultradnsPoolType == "rdpool" {
|
|
rdPoolObject, _ := p.newRDPoolObjectCreation(ctx, change)
|
|
record = udnssdk.RRSet{
|
|
RRType: change.ResourceRecordSetUltraDNS.RRType,
|
|
OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
RData: change.ResourceRecordSetUltraDNS.RData,
|
|
TTL: change.ResourceRecordSetUltraDNS.TTL,
|
|
Profile: rdPoolObject.RawProfile(),
|
|
}
|
|
} else {
|
|
return fmt.Errorf("we do not support Multiple target 'aaaa' records in sb pool please contact to neustar for further details")
|
|
}
|
|
} else {
|
|
record = udnssdk.RRSet{
|
|
RRType: change.ResourceRecordSetUltraDNS.RRType,
|
|
OwnerName: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
RData: change.ResourceRecordSetUltraDNS.RData,
|
|
TTL: change.ResourceRecordSetUltraDNS.TTL,
|
|
}
|
|
}
|
|
|
|
log.WithFields(log.Fields{
|
|
"record": record.OwnerName,
|
|
"type": record.RRType,
|
|
"ttl": record.TTL,
|
|
"action": change.Action,
|
|
"zone": zoneName,
|
|
"profile": record.Profile,
|
|
}).Info("Changing record.")
|
|
|
|
switch change.Action {
|
|
case ultradnsCreate:
|
|
if !p.dryRun {
|
|
res, err := p.client.RRSets.Create(rrsetKey, record)
|
|
_ = res
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
case ultradnsDelete:
|
|
err := p.getSpecificRecord(ctx, rrsetKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !p.dryRun {
|
|
_, err = p.client.RRSets.Delete(rrsetKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case ultradnsUpdate:
|
|
err := p.getSpecificRecord(ctx, rrsetKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !p.dryRun {
|
|
_, err = p.client.RRSets.Update(rrsetKey, record)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *UltraDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
|
combinedChanges := make([]*UltraDNSChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
|
log.Infof("value of changes %v,%v,%v", changes.Create, changes.UpdateNew, changes.Delete)
|
|
combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsCreate, changes.Create)...)
|
|
combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsUpdate, changes.UpdateNew)...)
|
|
combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsDelete, changes.Delete)...)
|
|
|
|
return p.submitChanges(ctx, combinedChanges)
|
|
}
|
|
|
|
func newUltraDNSChanges(action string, endpoints []*endpoint.Endpoint) []*UltraDNSChanges {
|
|
changes := make([]*UltraDNSChanges, 0, len(endpoints))
|
|
var ttl int
|
|
for _, e := range endpoints {
|
|
if e.RecordTTL.IsConfigured() {
|
|
ttl = int(e.RecordTTL)
|
|
}
|
|
|
|
// Adding suffix dot to the record name
|
|
recordName := fmt.Sprintf("%s.", e.DNSName)
|
|
change := &UltraDNSChanges{
|
|
Action: action,
|
|
ResourceRecordSetUltraDNS: udnssdk.RRSet{
|
|
RRType: e.RecordType,
|
|
OwnerName: recordName,
|
|
RData: e.Targets,
|
|
TTL: ttl,
|
|
},
|
|
}
|
|
changes = append(changes, change)
|
|
}
|
|
return changes
|
|
}
|
|
|
|
func seperateChangeByZone(zones []udnssdk.Zone, changes []*UltraDNSChanges) map[string][]*UltraDNSChanges {
|
|
change := make(map[string][]*UltraDNSChanges)
|
|
zoneNameID := provider.ZoneIDName{}
|
|
for _, z := range zones {
|
|
zoneNameID.Add(z.Properties.Name, z.Properties.Name)
|
|
change[z.Properties.Name] = []*UltraDNSChanges{}
|
|
}
|
|
|
|
for _, c := range changes {
|
|
zone, _ := zoneNameID.FindZone(c.ResourceRecordSetUltraDNS.OwnerName)
|
|
if zone == "" {
|
|
log.Infof("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSetUltraDNS.OwnerName)
|
|
continue
|
|
}
|
|
change[zone] = append(change[zone], c)
|
|
}
|
|
return change
|
|
}
|
|
|
|
func (p *UltraDNSProvider) getSpecificRecord(ctx context.Context, rrsetKey udnssdk.RRSetKey) (err error) {
|
|
_, err = p.client.RRSets.Select(rrsetKey)
|
|
if err != nil {
|
|
return fmt.Errorf("no record was found for %v", rrsetKey)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Creation of SBPoolObject
|
|
func (p *UltraDNSProvider) newSBPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (sbPool udnssdk.SBPoolProfile, err error) {
|
|
sbpoolRDataList := []udnssdk.SBRDataInfo{}
|
|
for range change.ResourceRecordSetUltraDNS.RData {
|
|
rrdataInfo := udnssdk.SBRDataInfo{
|
|
RunProbes: sbPoolRunProbes,
|
|
Priority: sbPoolPriority,
|
|
State: "NORMAL",
|
|
Threshold: 1,
|
|
Weight: nil,
|
|
}
|
|
sbpoolRDataList = append(sbpoolRDataList, rrdataInfo)
|
|
}
|
|
sbPoolObject := udnssdk.SBPoolProfile{
|
|
Context: udnssdk.SBPoolSchema,
|
|
Order: sbPoolOrder,
|
|
Description: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
MaxActive: len(change.ResourceRecordSetUltraDNS.RData),
|
|
MaxServed: len(change.ResourceRecordSetUltraDNS.RData),
|
|
RDataInfo: sbpoolRDataList,
|
|
RunProbes: sbPoolRunProbes,
|
|
ActOnProbes: sbPoolActOnProbes,
|
|
}
|
|
return sbPoolObject, nil
|
|
}
|
|
|
|
// Creation of RDPoolObject
|
|
func (p *UltraDNSProvider) newRDPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (rdPool udnssdk.RDPoolProfile, err error) {
|
|
rdPoolObject := udnssdk.RDPoolProfile{
|
|
Context: udnssdk.RDPoolSchema,
|
|
Order: rdPoolOrder,
|
|
Description: change.ResourceRecordSetUltraDNS.OwnerName,
|
|
}
|
|
return rdPoolObject, nil
|
|
}
|