Repository: kubernetes-sigs/external-dns (language: go)

Issue:
provider/bluecat: allow setting dns record TTL
<!-- Please only use this template for submitting enhancement requests. This can be something like a new provider or a new gateway.  -->

**What would you like to be added**:
Currently the bluecat provider does not allow setting the TTL on DNS records. As an end user I'd to to be able to customize the TTL using an annotation.

**Why is this needed**:
To allow setting a custom TTL on specific DNS records.



Discussion / hints:
/cc @vinny-sabatini 

FYI ... @seegeekrow @sanbornick

Fix patch:
diff --git a/provider/bluecat/bluecat.go b/provider/bluecat/bluecat.go
index ed6860aec4..31d080745b 100644
--- a/provider/bluecat/bluecat.go
+++ b/provider/bluecat/bluecat.go
@@ -210,9 +210,21 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
 		for _, rec := range resH {
 			propMap := splitProperties(rec.Properties)
 			ips := strings.Split(propMap["addresses"], ",")
-			for _, ip := range ips {
-				ep := endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeA, ip)
-				endpoints = append(endpoints, ep)
+			if _, ok := propMap["ttl"]; ok {
+				ttl, err := strconv.Atoi(propMap["ttl"])
+				if err != nil {
+					return nil, errors.Wrapf(err, "could not parse ttl '%d' as int for host record %v", ttl, rec.Name)
+				}
+
+				for _, ip := range ips {
+					ep := endpoint.NewEndpointWithTTL(propMap["absoluteName"], endpoint.RecordTypeA, endpoint.TTL(ttl), ip)
+					endpoints = append(endpoints, ep)
+				}
+			} else {
+				for _, ip := range ips {
+					ep := endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeA, ip)
+					endpoints = append(endpoints, ep)
+				}
 			}
 		}
 
@@ -223,7 +235,15 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
 		}
 		for _, rec := range resC {
 			propMap := splitProperties(rec.Properties)
-			endpoints = append(endpoints, endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeCNAME, propMap["linkedRecordName"]))
+			if _, ok := propMap["ttl"]; ok {
+				ttl, err := strconv.Atoi(propMap["ttl"])
+				if err != nil {
+					return nil, errors.Wrapf(err, "could not parse ttl '%d' as int for CNAME record %v", ttl, rec.Name)
+				}
+				endpoints = append(endpoints, endpoint.NewEndpointWithTTL(propMap["absoluteName"], endpoint.RecordTypeCNAME, endpoint.TTL(ttl), propMap["linkedRecordName"]))
+			} else {
+				endpoints = append(endpoints, endpoint.NewEndpoint(propMap["absoluteName"], endpoint.RecordTypeCNAME, propMap["linkedRecordName"]))
+			}
 		}
 
 		var resT []BluecatTXTRecord
@@ -455,11 +475,10 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
 	switch ep.RecordType {
 	case endpoint.RecordTypeA:
 		var res []BluecatHostRecord
-		// TODO Allow configurable properties/ttl
 		obj := bluecatCreateHostRecordRequest{
 			AbsoluteName: ep.DNSName,
 			IP4Address:   ep.Targets[0],
-			TTL:          0,
+			TTL:          int(ep.RecordTTL),
 			Properties:   "",
 		}
 		if getObject {
@@ -479,7 +498,7 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
 		obj := bluecatCreateCNAMERecordRequest{
 			AbsoluteName: ep.DNSName,
 			LinkedRecord: ep.Targets[0],
-			TTL:          0,
+			TTL:          int(ep.RecordTTL),
 			Properties:   "",
 		}
 		if getObject {
@@ -496,6 +515,8 @@ func (p *BluecatProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (reco
 		}
 	case endpoint.RecordTypeTXT:
 		var res []BluecatTXTRecord
+		// TODO: Allow setting TTL
+		// This is not implemented in the Bluecat Gateway
 		obj := bluecatCreateTXTRecordRequest{
 			AbsoluteName: ep.DNSName,
 			Text:         ep.Targets[0],


Test patch:
diff --git a/provider/bluecat/bluecat_test.go b/provider/bluecat/bluecat_test.go
index 1ec751d701..a94ba48346 100644
--- a/provider/bluecat/bluecat_test.go
+++ b/provider/bluecat/bluecat_test.go
@@ -127,8 +127,8 @@ func createMockBluecatZone(fqdn string) BluecatZone {
 	}
 }
 
-func createMockBluecatHostRecord(fqdn, target string) BluecatHostRecord {
-	props := "absoluteName=" + fqdn + "|addresses=" + target + "|"
+func createMockBluecatHostRecord(fqdn, target string, ttl int) BluecatHostRecord {
+	props := "absoluteName=" + fqdn + "|addresses=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
 	nameParts := strings.Split(fqdn, ".")
 	return BluecatHostRecord{
 		Name:       nameParts[0],
@@ -137,8 +137,8 @@ func createMockBluecatHostRecord(fqdn, target string) BluecatHostRecord {
 	}
 }
 
-func createMockBluecatCNAME(alias, target string) BluecatCNAMERecord {
-	props := "absoluteName=" + alias + "|linkedRecordName=" + target + "|"
+func createMockBluecatCNAME(alias, target string, ttl int) BluecatCNAMERecord {
+	props := "absoluteName=" + alias + "|linkedRecordName=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
 	nameParts := strings.Split(alias, ".")
 	return BluecatCNAMERecord{
 		Name:       nameParts[0],
@@ -175,21 +175,25 @@ var tests = bluecatTestData{
 				DNSName:    "example.com",
 				RecordType: endpoint.RecordTypeA,
 				Targets:    endpoint.Targets{"123.123.123.122"},
+				RecordTTL:  endpoint.TTL(30),
 			},
 			{
 				DNSName:    "nginx.example.com",
 				RecordType: endpoint.RecordTypeA,
 				Targets:    endpoint.Targets{"123.123.123.123"},
+				RecordTTL:  endpoint.TTL(30),
 			},
 			{
 				DNSName:    "whitespace.example.com",
 				RecordType: endpoint.RecordTypeA,
 				Targets:    endpoint.Targets{"123.123.123.124"},
+				RecordTTL:  endpoint.TTL(30),
 			},
 			{
 				DNSName:    "hack.example.com",
 				RecordType: endpoint.RecordTypeCNAME,
 				Targets:    endpoint.Targets{"bluecatnetworks.com"},
+				RecordTTL:  endpoint.TTL(30),
 			},
 			{
 				DNSName:    "abc.example.com",
@@ -206,12 +210,12 @@ func TestBluecatRecords(t *testing.T) {
 			createMockBluecatZone("example.com"),
 		},
 		mockBluecatHosts: &[]BluecatHostRecord{
-			createMockBluecatHostRecord("example.com", "123.123.123.122"),
-			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123"),
-			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124"),
+			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
+			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
+			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
 		},
 		mockBluecatCNAMEs: &[]BluecatCNAMERecord{
-			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com"),
+			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
 		},
 		mockBluecatTXTs: &[]BluecatTXTRecord{
 			createMockBluecatTXT("abc.example.com", "hello"),
@@ -264,12 +268,12 @@ func TestBluecatApplyChangesDelete(t *testing.T) {
 			createMockBluecatZone("example.com"),
 		},
 		mockBluecatHosts: &[]BluecatHostRecord{
-			createMockBluecatHostRecord("example.com", "123.123.123.122"),
-			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123"),
-			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124"),
+			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
+			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
+			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
 		},
 		mockBluecatCNAMEs: &[]BluecatCNAMERecord{
-			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com"),
+			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
 		},
 		mockBluecatTXTs: &[]BluecatTXTRecord{
 			createMockBluecatTXT("abc.example.com", "hello"),
@@ -308,12 +312,12 @@ func TestBluecatRecordset(t *testing.T) {
 			createMockBluecatZone("example.com"),
 		},
 		mockBluecatHosts: &[]BluecatHostRecord{
-			createMockBluecatHostRecord("example.com", "123.123.123.122"),
-			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123"),
-			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124"),
+			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
+			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
+			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
 		},
 		mockBluecatCNAMEs: &[]BluecatCNAMERecord{
-			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com"),
+			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
 		},
 		mockBluecatTXTs: &[]BluecatTXTRecord{
 			createMockBluecatTXT("abc.example.com", "hello"),
@@ -351,7 +355,7 @@ func TestBluecatRecordset(t *testing.T) {
 		IP4Address:   testHostEndpoint.Targets[0],
 	}
 	hostRecords := []BluecatHostRecord{
-		createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124"),
+		createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
 	}
 	hostExpected := bluecatRecordSet{
 		obj: &hostObj,
@@ -371,7 +375,7 @@ func TestBluecatRecordset(t *testing.T) {
 		LinkedRecord: testCnameEndpoint.Targets[0],
 	}
 	cnameRecords := []BluecatCNAMERecord{
-		createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com"),
+		createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
 	}
 	cnameExpected := bluecatRecordSet{
 		obj: &cnameObj,