Repository: xenolf/lego
Pull request #603 (2018-07-20 12:54:50)

Route53: Make it possible to configure from the env
Following up from #588, this allows the default route53.Config to be created from values in the environment.  


Patch:
diff --git a/platform/config/env/env.go b/platform/config/env/env.go
index 1267def7..267adcda 100644
--- a/platform/config/env/env.go
+++ b/platform/config/env/env.go
@@ -3,6 +3,7 @@ package env
 import (
 	"fmt"
 	"os"
+	"strconv"
 	"strings"
 )
 
@@ -25,3 +26,14 @@ func Get(names ...string) (map[string]string, error) {
 
 	return values, nil
 }
+
+// GetOrDefaultInt returns the given environment variable value as an integer.
+// Returns the default if the envvar cannot be coopered to an int, or is not found.
+func GetOrDefaultInt(envVar string, defaultValue int) int {
+	v, err := strconv.Atoi(os.Getenv(envVar))
+	if err != nil {
+		return defaultValue
+	}
+
+	return v
+}
diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go
index d7cc4c71..b0adca63 100644
--- a/providers/dns/route53/route53.go
+++ b/providers/dns/route53/route53.go
@@ -16,6 +16,7 @@ import (
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/route53"
 	"github.com/xenolf/lego/acme"
+	"github.com/xenolf/lego/platform/config/env"
 )
 
 // Config is used to configure the creation of the DNSProvider
@@ -29,11 +30,13 @@ type Config struct {
 
 // NewDefaultConfig returns a default configuration for the DNSProvider
 func NewDefaultConfig() *Config {
+	propagationMins := env.GetOrDefaultInt("AWS_PROPAGATION_TIMEOUT", 2)
+	intervalSecs := env.GetOrDefaultInt("AWS_POLLING_INTERVAL", 4)
 	return &Config{
-		MaxRetries:         5,
-		TTL:                10,
-		PropagationTimeout: time.Minute * 2,
-		PollingInterval:    time.Second * 4,
+		MaxRetries:         env.GetOrDefaultInt("AWS_MAX_RETRIES", 5),
+		TTL:                env.GetOrDefaultInt("AWS_TTL", 10),
+		PropagationTimeout: time.Minute * time.Duration(propagationMins),
+		PollingInterval:    time.Second * time.Duration(intervalSecs),
 		HostedZoneID:       os.Getenv("AWS_HOSTED_ZONE_ID"),
 	}
 }


Test patch:
diff --git a/platform/config/env/env_test.go b/platform/config/env/env_test.go
new file mode 100644
index 00000000..d3c92dc2
--- /dev/null
+++ b/platform/config/env/env_test.go
@@ -0,0 +1,56 @@
+package env
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_GetOrDefaultInt(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		envValue     string
+		defaultValue int
+		expected     int
+	}{
+		{
+			desc:         "valid value",
+			envValue:     "100",
+			defaultValue: 2,
+			expected:     100,
+		},
+		{
+			desc:         "invalid content, use default value",
+			envValue:     "abc123",
+			defaultValue: 2,
+			expected:     2,
+		},
+		{
+			desc:         "valid negative value",
+			envValue:     "-111",
+			defaultValue: 2,
+			expected:     -111,
+		},
+		{
+			desc:         "float: invalid type, use default value",
+			envValue:     "1.11",
+			defaultValue: 2,
+			expected:     2,
+		},
+	}
+
+	const key = "LEGO_ENV_TC"
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			defer os.Unsetenv(key)
+			err := os.Setenv(key, test.envValue)
+			require.NoError(t, err)
+
+			result := GetOrDefaultInt(key, test.defaultValue)
+			assert.Equal(t, test.expected, result)
+		})
+	}
+}
diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go
index b641a90e..886f1889 100644
--- a/providers/dns/route53/route53_integration_test.go
+++ b/providers/dns/route53/route53_integration_test.go
@@ -1,18 +1,17 @@
 package route53
 
 import (
-	"fmt"
-	"os"
 	"testing"
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/route53"
 	"github.com/stretchr/testify/require"
+	"github.com/xenolf/lego/platform/config/env"
 )
 
 func TestRoute53TTL(t *testing.T) {
-	m, err := testGetAndPreCheck()
+	config, err := env.Get("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION", "R53_DOMAIN")
 	if err != nil {
 		t.Skip(err.Error())
 	}
@@ -20,16 +19,16 @@ func TestRoute53TTL(t *testing.T) {
 	provider, err := NewDNSProvider()
 	require.NoError(t, err)
 
-	err = provider.Present(m["route53Domain"], "foo", "bar")
+	err = provider.Present(config["R53_DOMAIN"], "foo", "bar")
 	require.NoError(t, err)
 
 	// we need a separate R53 client here as the one in the DNS provider is
 	// unexported.
-	fqdn := "_acme-challenge." + m["route53Domain"] + "."
+	fqdn := "_acme-challenge." + config["R53_DOMAIN"] + "."
 	svc := route53.New(session.New())
 	zoneID, err := provider.getHostedZoneID(fqdn)
 	if err != nil {
-		provider.CleanUp(m["route53Domain"], "foo", "bar")
+		provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
 		t.Fatal(err)
 	}
 
@@ -38,32 +37,17 @@ func TestRoute53TTL(t *testing.T) {
 	}
 	resp, err := svc.ListResourceRecordSets(params)
 	if err != nil {
-		provider.CleanUp(m["route53Domain"], "foo", "bar")
+		provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
 		t.Fatal(err)
 	}
 
 	for _, v := range resp.ResourceRecordSets {
 		if aws.StringValue(v.Name) == fqdn && aws.StringValue(v.Type) == "TXT" && aws.Int64Value(v.TTL) == 10 {
-			provider.CleanUp(m["route53Domain"], "foo", "bar")
+			provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
 			return
 		}
 	}
 
-	provider.CleanUp(m["route53Domain"], "foo", "bar")
-	t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", m["route53Domain"])
-}
-
-func testGetAndPreCheck() (map[string]string, error) {
-	m := map[string]string{
-		"route53Key":    os.Getenv("AWS_ACCESS_KEY_ID"),
-		"route53Secret": os.Getenv("AWS_SECRET_ACCESS_KEY"),
-		"route53Region": os.Getenv("AWS_REGION"),
-		"route53Domain": os.Getenv("R53_DOMAIN"),
-	}
-	for _, v := range m {
-		if v == "" {
-			return nil, fmt.Errorf("AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and R53_DOMAIN are needed to run this test")
-		}
-	}
-	return m, nil
+	provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
+	t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", config["R53_DOMAIN"])
 }
diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go
index 6036082d..982d5725 100644
--- a/providers/dns/route53/route53_test.go
+++ b/providers/dns/route53/route53_test.go
@@ -4,6 +4,7 @@ import (
 	"net/http/httptest"
 	"os"
 	"testing"
+	"time"
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/credentials"
@@ -13,24 +14,40 @@ import (
 )
 
 var (
-	route53Secret string
-	route53Key    string
-	route53Region string
-	route53Zone   string
+	r53AwsSecretAccessKey string
+	r53AwsAccessKeyID     string
+	r53AwsRegion          string
+	r53AwsHostedZoneID    string
+
+	r53AwsMaxRetries         string
+	r53AwsTTL                string
+	r53AwsPropagationTimeout string
+	r53AwsPollingInterval    string
 )
 
 func init() {
-	route53Key = os.Getenv("AWS_ACCESS_KEY_ID")
-	route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY")
-	route53Region = os.Getenv("AWS_REGION")
-	route53Zone = os.Getenv("AWS_HOSTED_ZONE_ID")
+	r53AwsAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID")
+	r53AwsSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
+	r53AwsRegion = os.Getenv("AWS_REGION")
+	r53AwsHostedZoneID = os.Getenv("AWS_HOSTED_ZONE_ID")
+
+	r53AwsMaxRetries = os.Getenv("AWS_MAX_RETRIES")
+	r53AwsTTL = os.Getenv("AWS_TTL")
+	r53AwsPropagationTimeout = os.Getenv("AWS_PROPAGATION_TIMEOUT")
+	r53AwsPollingInterval = os.Getenv("AWS_POLLING_INTERVAL")
 }
 
 func restoreEnv() {
-	os.Setenv("AWS_ACCESS_KEY_ID", route53Key)
-	os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret)
-	os.Setenv("AWS_REGION", route53Region)
-	os.Setenv("AWS_HOSTED_ZONE_ID", route53Zone)
+	os.Setenv("AWS_ACCESS_KEY_ID", r53AwsAccessKeyID)
+	os.Setenv("AWS_SECRET_ACCESS_KEY", r53AwsSecretAccessKey)
+	os.Setenv("AWS_REGION", r53AwsRegion)
+	os.Setenv("AWS_HOSTED_ZONE_ID", r53AwsHostedZoneID)
+
+	os.Setenv("AWS_MAX_RETRIES", r53AwsMaxRetries)
+	os.Setenv("AWS_TTL", r53AwsTTL)
+	os.Setenv("AWS_PROPAGATION_TIMEOUT", r53AwsPropagationTimeout)
+	os.Setenv("AWS_POLLING_INTERVAL", r53AwsPollingInterval)
+
 }
 
 func makeRoute53Provider(ts *httptest.Server) *DNSProvider {
@@ -84,6 +101,27 @@ func TestHostedZoneIDFromEnv(t *testing.T) {
 	assert.Equal(t, testZoneID, fqdn)
 }
 
+func TestConfigFromEnv(t *testing.T) {
+	defer restoreEnv()
+
+	config := NewDefaultConfig()
+	assert.Equal(t, config.TTL, 10, "Expected TTL to be use the default")
+
+	os.Setenv("AWS_MAX_RETRIES", "10")
+	os.Setenv("AWS_TTL", "99")
+	os.Setenv("AWS_PROPAGATION_TIMEOUT", "60")
+	os.Setenv("AWS_POLLING_INTERVAL", "60")
+	const zoneID = "abc123"
+	os.Setenv("AWS_HOSTED_ZONE_ID", zoneID)
+
+	config = NewDefaultConfig()
+	assert.Equal(t, config.MaxRetries, 10, "Expected PropagationTimeout to be configured from the environment")
+	assert.Equal(t, config.TTL, 99, "Expected TTL to be configured from the environment")
+	assert.Equal(t, config.PropagationTimeout, time.Minute*60, "Expected PropagationTimeout to be configured from the environment")
+	assert.Equal(t, config.PollingInterval, time.Second*60, "Expected PollingInterval to be configured from the environment")
+	assert.Equal(t, config.HostedZoneID, zoneID, "Expected HostedZoneID to be configured from the environment")
+}
+
 func TestRoute53Present(t *testing.T) {
 	mockResponses := MockResponseMap{
 		"/2013-04-01/hostedzonesbyname":         MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse},