liquidweb: add LWAPI_ prefix for env vars (#2034)

This commit is contained in:
Ludovic Fernandez 2023-10-15 01:12:55 +02:00 committed by GitHub
parent 8afdc9d01c
commit 52990b3c9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 52 deletions

View file

@ -78,15 +78,26 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) {
return values, nil return values, nil
} }
func GetOneWithFallback[T any](main string, defaultValue T, fn func(string) (T, error), names ...string) T {
v, _ := getOneWithFallback(main, names...)
value, err := fn(v)
if err != nil {
return defaultValue
}
return value
}
func getOneWithFallback(main string, names ...string) (string, string) { func getOneWithFallback(main string, names ...string) (string, string) {
value := GetOrFile(main) value := GetOrFile(main)
if len(value) > 0 { if value != "" {
return value, main return value, main
} }
for _, name := range names { for _, name := range names {
value := GetOrFile(name) value := GetOrFile(name)
if len(value) > 0 { if value != "" {
return value, main return value, main
} }
} }
@ -94,43 +105,32 @@ func getOneWithFallback(main string, names ...string) (string, string) {
return "", main return "", main
} }
// GetOrDefaultInt returns the given environment variable value as an integer.
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultInt(envVar string, defaultValue int) int {
v, err := strconv.Atoi(GetOrFile(envVar))
if err != nil {
return defaultValue
}
return v
}
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
v := GetOrDefaultInt(envVar, -1)
if v < 0 {
return defaultValue
}
return time.Duration(v) * time.Second
}
// GetOrDefaultString returns the given environment variable value as a string. // GetOrDefaultString returns the given environment variable value as a string.
// Returns the default if the env var cannot be found. // Returns the default if the env var cannot be found.
func GetOrDefaultString(envVar, defaultValue string) string { func GetOrDefaultString(envVar string, defaultValue string) string {
v := GetOrFile(envVar) return getOrDefault(envVar, defaultValue, ParseString)
if v == "" {
return defaultValue
}
return v
} }
// GetOrDefaultBool returns the given environment variable value as a boolean. // GetOrDefaultBool returns the given environment variable value as a boolean.
// Returns the default if the env var cannot be coopered to a boolean, or is not found. // Returns the default if the env var cannot be coopered to a boolean, or is not found.
func GetOrDefaultBool(envVar string, defaultValue bool) bool { func GetOrDefaultBool(envVar string, defaultValue bool) bool {
v, err := strconv.ParseBool(GetOrFile(envVar)) return getOrDefault(envVar, defaultValue, strconv.ParseBool)
}
// GetOrDefaultInt returns the given environment variable value as an integer.
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultInt(envVar string, defaultValue int) int {
return getOrDefault(envVar, defaultValue, strconv.Atoi)
}
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
return getOrDefault(envVar, defaultValue, ParseSecond)
}
func getOrDefault[T any](envVar string, defaultValue T, fn func(string) (T, error)) T {
v, err := fn(GetOrFile(envVar))
if err != nil { if err != nil {
return defaultValue return defaultValue
} }
@ -161,3 +161,26 @@ func GetOrFile(envVar string) string {
return strings.TrimSuffix(string(fileContents), "\n") return strings.TrimSuffix(string(fileContents), "\n")
} }
// ParseSecond parses env var value (string) to a second (time.Duration).
func ParseSecond(s string) (time.Duration, error) {
v, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if v < 0 {
return 0, fmt.Errorf("unsupported value: %d", v)
}
return time.Duration(v) * time.Second, nil
}
// ParseString parses env var value (string) to a string but throws an error when the string is empty.
func ParseString(s string) (string, error) {
if s == "" {
return "", errors.New("empty string")
}
return s, nil
}

View file

@ -15,12 +15,12 @@ func TestGetWithFallback(t *testing.T) {
var1Missing := os.Getenv("TEST_LEGO_VAR_MISSING_1") var1Missing := os.Getenv("TEST_LEGO_VAR_MISSING_1")
var2Missing := os.Getenv("TEST_LEGO_VAR_MISSING_2") var2Missing := os.Getenv("TEST_LEGO_VAR_MISSING_2")
defer func() { t.Cleanup(func() {
_ = os.Setenv("TEST_LEGO_VAR_EXIST_1", var1Exist) _ = os.Setenv("TEST_LEGO_VAR_EXIST_1", var1Exist)
_ = os.Setenv("TEST_LEGO_VAR_EXIST_2", var2Exist) _ = os.Setenv("TEST_LEGO_VAR_EXIST_2", var2Exist)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_1", var1Missing) _ = os.Setenv("TEST_LEGO_VAR_MISSING_1", var1Missing)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_2", var2Missing) _ = os.Setenv("TEST_LEGO_VAR_MISSING_2", var2Missing)
}() })
err := os.Setenv("TEST_LEGO_VAR_EXIST_1", "VAR1") err := os.Setenv("TEST_LEGO_VAR_EXIST_1", "VAR1")
require.NoError(t, err) require.NoError(t, err)
@ -93,7 +93,10 @@ func TestGetWithFallback(t *testing.T) {
} }
for _, test := range testCases { for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel()
value, err := GetWithFallback(test.groups...) value, err := GetWithFallback(test.groups...)
if len(test.expected.error) > 0 { if len(test.expected.error) > 0 {
assert.EqualError(t, err, test.expected.error) assert.EqualError(t, err, test.expected.error)
@ -105,6 +108,74 @@ func TestGetWithFallback(t *testing.T) {
} }
} }
func TestGetOneWithFallback(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")
t.Cleanup(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)
testCases := []struct {
desc string
main string
defaultValue string
alts []string
expected string
}{
{
desc: "with value and no alternative",
main: "TEST_LEGO_VAR_EXIST_1",
defaultValue: "oops",
expected: "VAR1",
},
{
desc: "with value and alternatives",
main: "TEST_LEGO_VAR_EXIST_1",
defaultValue: "oops",
alts: []string{"TEST_LEGO_VAR_MISSING_1"},
expected: "VAR1",
},
{
desc: "without value and no alternatives",
main: "TEST_LEGO_VAR_MISSING_1",
defaultValue: "oops",
expected: "oops",
},
{
desc: "without value and alternatives",
main: "TEST_LEGO_VAR_MISSING_1",
defaultValue: "oops",
alts: []string{"TEST_LEGO_VAR_EXIST_1"},
expected: "VAR1",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
value := GetOneWithFallback(test.main, test.defaultValue, ParseString, test.alts...)
assert.Equal(t, test.expected, value)
})
}
}
func TestGetOrDefaultInt(t *testing.T) { func TestGetOrDefaultInt(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string

View file

@ -20,7 +20,8 @@ const defaultBaseURL = "https://api.liquidweb.com"
// Environment variables names. // Environment variables names.
const ( const (
envNamespace = "LIQUID_WEB_" envNamespace = "LIQUID_WEB_"
altEnvNamespace = "LWAPI_"
EnvURL = envNamespace + "URL" EnvURL = envNamespace + "URL"
EnvUsername = envNamespace + "USERNAME" EnvUsername = envNamespace + "USERNAME"
@ -49,10 +50,10 @@ type Config struct {
func NewDefaultConfig() *Config { func NewDefaultConfig() *Config {
return &Config{ return &Config{
BaseURL: defaultBaseURL, BaseURL: defaultBaseURL,
TTL: env.GetOrDefaultInt(EnvTTL, 300), TTL: env.GetOneWithFallback(EnvTTL, 300, strconv.Atoi, altEnvName(EnvTTL)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, 2*time.Minute, env.ParseSecond, altEnvName(EnvPropagationTimeout)),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), PollingInterval: env.GetOneWithFallback(EnvPollingInterval, 2*time.Second, env.ParseSecond, altEnvName(EnvPollingInterval)),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 1*time.Minute), HTTPTimeout: env.GetOneWithFallback(EnvHTTPTimeout, 1*time.Minute, env.ParseSecond, altEnvName(EnvHTTPTimeout)),
} }
} }
@ -66,16 +67,19 @@ type DNSProvider struct {
// NewDNSProvider returns a DNSProvider instance configured for Liquid Web. // NewDNSProvider returns a DNSProvider instance configured for Liquid Web.
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvUsername, EnvPassword) values, err := env.GetWithFallback(
[]string{EnvUsername, altEnvName(EnvUsername)},
[]string{EnvPassword, altEnvName(EnvPassword)},
)
if err != nil { if err != nil {
return nil, fmt.Errorf("liquidweb: %w", err) return nil, fmt.Errorf("liquidweb: %w", err)
} }
config := NewDefaultConfig() config := NewDefaultConfig()
config.BaseURL = env.GetOrFile(EnvURL) config.BaseURL = env.GetOneWithFallback(EnvURL, defaultBaseURL, env.ParseString, altEnvName(EnvURL))
config.Username = values[EnvUsername] config.Username = values[EnvUsername]
config.Password = values[EnvPassword] config.Password = values[EnvPassword]
config.Zone = env.GetOrDefaultString(EnvZone, "") config.Zone = env.GetOneWithFallback(EnvZone, "", env.ParseString, altEnvName(EnvZone))
return NewDNSProviderConfig(config) return NewDNSProviderConfig(config)
} }
@ -191,3 +195,7 @@ func (d *DNSProvider) findZone(domain string) (string, error) {
return zs[0].Name, nil return zs[0].Name, nil
} }
func altEnvName(v string) string {
return strings.ReplaceAll(v, envNamespace, altEnvNamespace)
}

View file

@ -5,22 +5,22 @@ Code = "liquidweb"
Since = "v3.1.0" Since = "v3.1.0"
Example = ''' Example = '''
LIQUID_WEB_USERNAME=someuser \ LWAPI_USERNAME=someuser \
LIQUID_WEB_PASSWORD=somepass \ LWAPI_PASSWORD=somepass \
lego --email you@example.com --dns liquidweb --domains my.example.org run lego --email you@example.com --dns liquidweb --domains my.example.org run
''' '''
[Configuration] [Configuration]
[Configuration.Credentials] [Configuration.Credentials]
LIQUID_WEB_USERNAME = "Liquid Web API Username" LWAPI_USERNAME = "Liquid Web API Username"
LIQUID_WEB_PASSWORD = "Liquid Web API Password" LWAPI_PASSWORD = "Liquid Web API Password"
[Configuration.Additional] [Configuration.Additional]
LIQUID_WEB_ZONE = "DNS Zone" LWAPI_ZONE = "DNS Zone"
LIQUID_WEB_URL = "Liquid Web API endpoint" LWAPI_URL = "Liquid Web API endpoint"
LIQUID_WEB_TTL = "The TTL of the TXT record used for the DNS challenge" LWAPI_TTL = "The TTL of the TXT record used for the DNS challenge"
LIQUID_WEB_POLLING_INTERVAL = "Time between DNS propagation check" LWAPI_POLLING_INTERVAL = "Time between DNS propagation check"
LIQUID_WEB_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" LWAPI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
LIQUID_WEB_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)" LWAPI_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)"
[Links] [Links]
API = "https://api.liquidweb.com/docs/" API = "https://api.liquidweb.com/docs/"