Skip to content

Commit

Permalink
Add support for human-friendly TTL values
Browse files Browse the repository at this point in the history
Supports specifying TTL values in Golang duration format for
`external-dns.alpha.kubernetes.io/ttl` annotation.
  • Loading branch information
hypnoglow committed Oct 29, 2019
1 parent cec75d7 commit 3850c16
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 3 deletions.
17 changes: 16 additions & 1 deletion docs/ttl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.:

Expand All @@ -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
=========
Expand Down
14 changes: 14 additions & 0 deletions source/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,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{
{
Expand All @@ -948,6 +957,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),
},
},
},
{
Expand Down
15 changes: 15 additions & 0 deletions source/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,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{
{
Expand All @@ -828,6 +838,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),
},
},
},
{
Expand Down
14 changes: 14 additions & 0 deletions source/ingressroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand All @@ -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),
},
},
},
{
Expand Down
24 changes: 24 additions & 0 deletions source/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,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",
"",
Expand Down
18 changes: 17 additions & 1 deletion source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"net"
"strconv"
"strings"
"time"

"github.com/kubernetes-incubator/external-dns/endpoint"
)
Expand Down Expand Up @@ -63,7 +64,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)
}
Expand All @@ -73,6 +74,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 {
Expand Down
14 changes: 13 additions & 1 deletion source/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,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)
Expand Down

0 comments on commit 3850c16

Please sign in to comment.