Route53: Make it possible to configure from the env (#603)

This commit is contained in:
Conor Mongey 2018-09-08 12:49:24 +01:00 committed by Ludovic Fernandez
parent 725b6b816a
commit ef7cd04002
5 changed files with 134 additions and 41 deletions

View file

@ -3,6 +3,7 @@ package env
import ( import (
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
) )
@ -25,3 +26,14 @@ func Get(names ...string) (map[string]string, error) {
return values, nil 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
}

56
platform/config/env/env_test.go vendored Normal file
View file

@ -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)
})
}
}

View file

@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/route53"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/platform/config/env"
) )
// Config is used to configure the creation of the DNSProvider // 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 // NewDefaultConfig returns a default configuration for the DNSProvider
func NewDefaultConfig() *Config { func NewDefaultConfig() *Config {
propagationMins := env.GetOrDefaultInt("AWS_PROPAGATION_TIMEOUT", 2)
intervalSecs := env.GetOrDefaultInt("AWS_POLLING_INTERVAL", 4)
return &Config{ return &Config{
MaxRetries: 5, MaxRetries: env.GetOrDefaultInt("AWS_MAX_RETRIES", 5),
TTL: 10, TTL: env.GetOrDefaultInt("AWS_TTL", 10),
PropagationTimeout: time.Minute * 2, PropagationTimeout: time.Minute * time.Duration(propagationMins),
PollingInterval: time.Second * 4, PollingInterval: time.Second * time.Duration(intervalSecs),
HostedZoneID: os.Getenv("AWS_HOSTED_ZONE_ID"), HostedZoneID: os.Getenv("AWS_HOSTED_ZONE_ID"),
} }
} }

View file

@ -1,18 +1,17 @@
package route53 package route53
import ( import (
"fmt"
"os"
"testing" "testing"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/route53"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/xenolf/lego/platform/config/env"
) )
func TestRoute53TTL(t *testing.T) { 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 { if err != nil {
t.Skip(err.Error()) t.Skip(err.Error())
} }
@ -20,16 +19,16 @@ func TestRoute53TTL(t *testing.T) {
provider, err := NewDNSProvider() provider, err := NewDNSProvider()
require.NoError(t, err) require.NoError(t, err)
err = provider.Present(m["route53Domain"], "foo", "bar") err = provider.Present(config["R53_DOMAIN"], "foo", "bar")
require.NoError(t, err) require.NoError(t, err)
// we need a separate R53 client here as the one in the DNS provider is // we need a separate R53 client here as the one in the DNS provider is
// unexported. // unexported.
fqdn := "_acme-challenge." + m["route53Domain"] + "." fqdn := "_acme-challenge." + config["R53_DOMAIN"] + "."
svc := route53.New(session.New()) svc := route53.New(session.New())
zoneID, err := provider.getHostedZoneID(fqdn) zoneID, err := provider.getHostedZoneID(fqdn)
if err != nil { if err != nil {
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
t.Fatal(err) t.Fatal(err)
} }
@ -38,32 +37,17 @@ func TestRoute53TTL(t *testing.T) {
} }
resp, err := svc.ListResourceRecordSets(params) resp, err := svc.ListResourceRecordSets(params)
if err != nil { if err != nil {
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
t.Fatal(err) t.Fatal(err)
} }
for _, v := range resp.ResourceRecordSets { for _, v := range resp.ResourceRecordSets {
if aws.StringValue(v.Name) == fqdn && aws.StringValue(v.Type) == "TXT" && aws.Int64Value(v.TTL) == 10 { 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 return
} }
} }
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(config["R53_DOMAIN"], "foo", "bar")
t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", m["route53Domain"]) t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", config["R53_DOMAIN"])
}
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
} }

View file

@ -4,6 +4,7 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"testing" "testing"
"time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
@ -13,24 +14,40 @@ import (
) )
var ( var (
route53Secret string r53AwsSecretAccessKey string
route53Key string r53AwsAccessKeyID string
route53Region string r53AwsRegion string
route53Zone string r53AwsHostedZoneID string
r53AwsMaxRetries string
r53AwsTTL string
r53AwsPropagationTimeout string
r53AwsPollingInterval string
) )
func init() { func init() {
route53Key = os.Getenv("AWS_ACCESS_KEY_ID") r53AwsAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID")
route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY") r53AwsSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
route53Region = os.Getenv("AWS_REGION") r53AwsRegion = os.Getenv("AWS_REGION")
route53Zone = os.Getenv("AWS_HOSTED_ZONE_ID") 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() { func restoreEnv() {
os.Setenv("AWS_ACCESS_KEY_ID", route53Key) os.Setenv("AWS_ACCESS_KEY_ID", r53AwsAccessKeyID)
os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret) os.Setenv("AWS_SECRET_ACCESS_KEY", r53AwsSecretAccessKey)
os.Setenv("AWS_REGION", route53Region) os.Setenv("AWS_REGION", r53AwsRegion)
os.Setenv("AWS_HOSTED_ZONE_ID", route53Zone) 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 { func makeRoute53Provider(ts *httptest.Server) *DNSProvider {
@ -84,6 +101,27 @@ func TestHostedZoneIDFromEnv(t *testing.T) {
assert.Equal(t, testZoneID, fqdn) 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) { func TestRoute53Present(t *testing.T) {
mockResponses := MockResponseMap{ mockResponses := MockResponseMap{
"/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse},