From 6e6af8aa0dbd10c273e460b2a322bfedcbdd0eb4 Mon Sep 17 00:00:00 2001 From: Igor Zibarev Date: Thu, 9 Jan 2020 21:13:39 +0300 Subject: [PATCH] Add support for human-friendly TTL values Supports specifying TTL values in Golang duration format for `external-dns.alpha.kubernetes.io/ttl` annotation. --- docs/ttl.md | 17 ++++++++++++++++- source/gateway_test.go | 14 ++++++++++++++ source/ingress_test.go | 15 +++++++++++++++ source/ingressroute_test.go | 14 ++++++++++++++ source/service_test.go | 24 ++++++++++++++++++++++++ source/source.go | 18 +++++++++++++++++- source/source_test.go | 14 +++++++++++++- 7 files changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/ttl.md b/docs/ttl.md index 57a262aab..8855131e1 100644 --- a/docs/ttl.md +++ b/docs/ttl.md @@ -2,6 +2,7 @@ Configure DNS record TTL (Time-To-Live) ======================================= An optional annotation `external-dns.alpha.kubernetes.io/ttl` is available to customize the TTL value of a DNS record. +TTL is specified as an integer encoded as string representing seconds. To configure it, simply annotate a service/ingress, e.g.: @@ -15,7 +16,21 @@ metadata: ... ``` -TTL must be a positive integer encoded as string. +TTL can also be specified as a duration value parsable by Golang [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration): + +```yaml +apiVersion: v1 +kind: Service +metadata: + annotations: + external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com. + external-dns.alpha.kubernetes.io/ttl: "1m" + ... +``` + +Both examples result in the same value of 60 seconds TTL. + +TTL must be a positive value. Providers ========= diff --git a/source/gateway_test.go b/source/gateway_test.go index c397e5be0..a1b6d5a62 100644 --- a/source/gateway_test.go +++ b/source/gateway_test.go @@ -933,6 +933,15 @@ func testGatewayEndpoints(t *testing.T) { }, dnsnames: [][]string{{"example2.org"}}, }, + { + name: "fake3", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "gateway-target.com", + ttlAnnotationKey: "10s", + }, + dnsnames: [][]string{{"example3.org"}}, + }, }, expected: []*endpoint.Endpoint{ { @@ -945,6 +954,11 @@ func testGatewayEndpoints(t *testing.T) { Targets: endpoint.Targets{"gateway-target.com"}, RecordTTL: endpoint.TTL(1), }, + { + DNSName: "example3.org", + Targets: endpoint.Targets{"gateway-target.com"}, + RecordTTL: endpoint.TTL(10), + }, }, }, { diff --git a/source/ingress_test.go b/source/ingress_test.go index cf9d2d50a..37a4efd4e 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -815,6 +815,16 @@ func testIngressEndpoints(t *testing.T) { dnsnames: []string{"example2.org"}, ips: []string{"8.8.8.8"}, }, + { + name: "fake3", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingress-target.com", + ttlAnnotationKey: "10s", + }, + dnsnames: []string{"example3.org"}, + ips: []string{"8.8.4.4"}, + }, }, expected: []*endpoint.Endpoint{ { @@ -827,6 +837,11 @@ func testIngressEndpoints(t *testing.T) { Targets: endpoint.Targets{"ingress-target.com"}, RecordTTL: endpoint.TTL(1), }, + { + DNSName: "example3.org", + Targets: endpoint.Targets{"ingress-target.com"}, + RecordTTL: endpoint.TTL(10), + }, }, }, { diff --git a/source/ingressroute_test.go b/source/ingressroute_test.go index 0f52120c9..b13f5b7f2 100644 --- a/source/ingressroute_test.go +++ b/source/ingressroute_test.go @@ -824,6 +824,15 @@ func testIngressRouteEndpoints(t *testing.T) { }, host: "example2.org", }, + { + name: "fake3", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingressroute-target.com", + ttlAnnotationKey: "10s", + }, + host: "example3.org", + }, }, expected: []*endpoint.Endpoint{ { @@ -836,6 +845,11 @@ func testIngressRouteEndpoints(t *testing.T) { Targets: endpoint.Targets{"ingressroute-target.com"}, RecordTTL: endpoint.TTL(1), }, + { + DNSName: "example3.org", + Targets: endpoint.Targets{"ingressroute-target.com"}, + RecordTTL: endpoint.TTL(10), + }, }, }, { diff --git a/source/service_test.go b/source/service_test.go index 3fbda307b..7acdbbc60 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -969,6 +969,30 @@ func testServiceSourceEndpoints(t *testing.T) { }, false, }, + { + "ttl annotated (in duration format) and is valid should set Record.TTL", + "", + "", + "testing", + "foo", + v1.ServiceTypeLoadBalancer, + "", + "", + false, + false, + map[string]string{}, + map[string]string{ + hostnameAnnotationKey: "foo.example.org.", + ttlAnnotationKey: "1m", + }, + "", + []string{"1.2.3.4"}, + []string{}, + []*endpoint.Endpoint{ + {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)}, + }, + false, + }, { "Negative ttl is not valid", "", diff --git a/source/source.go b/source/source.go index a82db8f76..61609f9e7 100644 --- a/source/source.go +++ b/source/source.go @@ -22,6 +22,7 @@ import ( "net" "strconv" "strings" + "time" "sigs.k8s.io/external-dns/endpoint" ) @@ -65,7 +66,7 @@ func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error) if !exists { return ttlNotConfigured, nil } - ttlValue, err := strconv.ParseInt(ttlAnnotation, 10, 64) + ttlValue, err := parseTTL(ttlAnnotation) if err != nil { return ttlNotConfigured, fmt.Errorf("\"%v\" is not a valid TTL value", ttlAnnotation) } @@ -75,6 +76,21 @@ func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error) return endpoint.TTL(ttlValue), nil } +// parseTTL parses TTL from string, returning duration in seconds. +// parseTTL supports both integers like "600" and durations based +// on Go Duration like "10m", hence "600" and "10m" represent the same value. +// +// Note: for durations like "1.5s" the fraction is omitted (resulting in 1 second +// for the example). +func parseTTL(s string) (ttlSeconds int64, err error) { + ttlDuration, err := time.ParseDuration(s) + if err != nil { + return strconv.ParseInt(s, 10, 64) + } + + return int64(ttlDuration.Seconds()), nil +} + func getHostnamesFromAnnotations(annotations map[string]string) []string { hostnameAnnotation, exists := annotations[hostnameAnnotationKey] if !exists { diff --git a/source/source_test.go b/source/source_test.go index a434c8470..04c3b4612 100644 --- a/source/source_test.go +++ b/source/source_test.go @@ -63,11 +63,23 @@ func TestGetTTLFromAnnotations(t *testing.T) { expectedErr: fmt.Errorf("TTL value must be between [%d, %d]", ttlMinimum, ttlMaximum), }, { - title: "TTL annotation value is set correctly", + title: "TTL annotation value is set correctly using integer", annotations: map[string]string{ttlAnnotationKey: "60"}, expectedTTL: endpoint.TTL(60), expectedErr: nil, }, + { + title: "TTL annotation value is set correctly using duration (whole)", + annotations: map[string]string{ttlAnnotationKey: "10m"}, + expectedTTL: endpoint.TTL(600), + expectedErr: nil, + }, + { + title: "TTL annotation value is set correcly using duration (fractional)", + annotations: map[string]string{ttlAnnotationKey: "20.5s"}, + expectedTTL: endpoint.TTL(20), + expectedErr: nil, + }, } { t.Run(tc.title, func(t *testing.T) { ttl, err := getTTLFromAnnotations(tc.annotations)