diff --git a/platform/config/env/env.go b/platform/config/env/env.go index 4da1b4b6..557d3a73 100644 --- a/platform/config/env/env.go +++ b/platform/config/env/env.go @@ -1,6 +1,7 @@ package env import ( + "errors" "fmt" "os" "strconv" @@ -28,6 +29,68 @@ func Get(names ...string) (map[string]string, error) { return values, nil } +// GetWithFallback Get environment variable values +// The first name in each group is use as key in the result map +// +// // LEGO_ONE="ONE" +// // LEGO_TWO="TWO" +// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"}) +// // => "LEGO_ONE" = "ONE" +// +// ---- +// +// // LEGO_ONE="" +// // LEGO_TWO="TWO" +// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"}) +// // => "LEGO_ONE" = "TWO" +// +// ---- +// +// // LEGO_ONE="" +// // LEGO_TWO="" +// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"}) +// // => error +// +func GetWithFallback(groups ...[]string) (map[string]string, error) { + values := map[string]string{} + + var missingEnvVars []string + for _, names := range groups { + if len(names) == 0 { + return nil, errors.New("undefined environment variable names") + } + + value, envVar := getOneWithFallback(names[0], names[1:]...) + if len(value) == 0 { + missingEnvVars = append(missingEnvVars, envVar) + continue + } + values[envVar] = value + } + + if len(missingEnvVars) > 0 { + return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ",")) + } + + return values, nil +} + +func getOneWithFallback(main string, names ...string) (string, string) { + value := os.Getenv(main) + if len(value) > 0 { + return value, main + } + + for _, name := range names { + value := os.Getenv(name) + if len(value) > 0 { + return value, main + } + } + + return "", main +} + // 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 { diff --git a/platform/config/env/env_test.go b/platform/config/env/env_test.go index c27e6da9..d32d80fd 100644 --- a/platform/config/env/env_test.go +++ b/platform/config/env/env_test.go @@ -9,6 +9,103 @@ import ( "github.com/stretchr/testify/require" ) +func TestGetWithFallback(t *testing.T) { + var1Exist := os.Getenv("TEST_LEGO_VAR_EXIST_1") + var2Exist := os.Getenv("TEST_LEGO_VAR_EXIST_2") + var1Missing := os.Getenv("TEST_LEGO_VAR_MISSING_1") + var2Missing := os.Getenv("TEST_LEGO_VAR_MISSING_2") + + defer func() { + _ = os.Setenv("TEST_LEGO_VAR_EXIST_1", var1Exist) + _ = os.Setenv("TEST_LEGO_VAR_EXIST_2", var2Exist) + _ = os.Setenv("TEST_LEGO_VAR_MISSING_1", var1Missing) + _ = os.Setenv("TEST_LEGO_VAR_MISSING_2", var2Missing) + }() + + err := os.Setenv("TEST_LEGO_VAR_EXIST_1", "VAR1") + require.NoError(t, err) + err = os.Setenv("TEST_LEGO_VAR_EXIST_2", "VAR2") + require.NoError(t, err) + err = os.Unsetenv("TEST_LEGO_VAR_MISSING_1") + require.NoError(t, err) + err = os.Unsetenv("TEST_LEGO_VAR_MISSING_2") + require.NoError(t, err) + + type expected struct { + value map[string]string + error string + } + + testCases := []struct { + desc string + groups [][]string + expected expected + }{ + { + desc: "no groups", + groups: nil, + expected: expected{ + value: map[string]string{}, + }, + }, + { + desc: "empty groups", + groups: [][]string{{}, {}}, + expected: expected{ + error: "undefined environment variable names", + }, + }, + { + desc: "missing env var", + groups: [][]string{{"TEST_LEGO_VAR_MISSING_1"}}, + expected: expected{ + error: "some credentials information are missing: TEST_LEGO_VAR_MISSING_1", + }, + }, + { + desc: "all env var in a groups are missing", + groups: [][]string{{"TEST_LEGO_VAR_MISSING_1", "TEST_LEGO_VAR_MISSING_2"}}, + expected: expected{ + error: "some credentials information are missing: TEST_LEGO_VAR_MISSING_1", + }, + }, + { + desc: "only the first env var have a value", + groups: [][]string{{"TEST_LEGO_VAR_EXIST_1", "TEST_LEGO_VAR_MISSING_1"}}, + expected: expected{ + value: map[string]string{"TEST_LEGO_VAR_EXIST_1": "VAR1"}, + }, + }, + { + desc: "only the second env var have a value", + groups: [][]string{{"TEST_LEGO_VAR_MISSING_1", "TEST_LEGO_VAR_EXIST_1"}}, + expected: expected{ + value: map[string]string{"TEST_LEGO_VAR_MISSING_1": "VAR1"}, + }, + }, + { + desc: "only all env vars have a value", + groups: [][]string{{"TEST_LEGO_VAR_EXIST_1", "TEST_LEGO_VAR_EXIST_2"}}, + expected: expected{ + value: map[string]string{"TEST_LEGO_VAR_EXIST_1": "VAR1"}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + value, err := GetWithFallback(test.groups...) + if len(test.expected.error) > 0 { + assert.EqualError(t, err, test.expected.error) + } else { + require.NoError(t, err) + assert.Equal(t, test.expected.value, value) + } + }) + } + +} + func TestGetOrDefaultInt(t *testing.T) { testCases := []struct { desc string diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index de15650f..b402bd82 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -47,7 +47,9 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: // CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get("CLOUDFLARE_EMAIL", "CLOUDFLARE_API_KEY") + values, err := env.GetWithFallback( + []string{"CLOUDFLARE_EMAIL", "CF_API_EMAIL"}, + []string{"CLOUDFLARE_API_KEY", "CF_API_KEY"}) if err != nil { return nil, fmt.Errorf("cloudflare: %v", err) } @@ -92,8 +94,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { }, nil } -// Timeout returns the timeout and interval to use when checking for DNS -// propagation. Adjusting here to cope with spikes in propagation times. +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval }