From ef7cd040022cbbda2836cd3026457208b0c9a2be Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Sat, 8 Sep 2018 12:49:24 +0100 Subject: [PATCH] Route53: Make it possible to configure from the env (#603) --- platform/config/env/env.go | 12 ++++ platform/config/env/env_test.go | 56 +++++++++++++++++ providers/dns/route53/route53.go | 11 ++-- .../dns/route53/route53_integration_test.go | 34 +++------- providers/dns/route53/route53_test.go | 62 +++++++++++++++---- 5 files changed, 134 insertions(+), 41 deletions(-) create mode 100644 platform/config/env/env_test.go 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/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.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"), } } 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},