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
}
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) {
value := GetOrFile(main)
if len(value) > 0 {
if value != "" {
return value, main
}
for _, name := range names {
value := GetOrFile(name)
if len(value) > 0 {
if value != "" {
return value, main
}
}
@ -94,43 +105,32 @@ func getOneWithFallback(main string, names ...string) (string, string) {
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.
// Returns the default if the env var cannot be found.
func GetOrDefaultString(envVar, defaultValue string) string {
v := GetOrFile(envVar)
if v == "" {
return defaultValue
}
return v
func GetOrDefaultString(envVar string, defaultValue string) string {
return getOrDefault(envVar, defaultValue, ParseString)
}
// 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.
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 {
return defaultValue
}
@ -161,3 +161,26 @@ func GetOrFile(envVar string) string {
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")
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_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)
@ -93,7 +93,10 @@ func TestGetWithFallback(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
value, err := GetWithFallback(test.groups...)
if len(test.expected.error) > 0 {
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) {
testCases := []struct {
desc string

View file

@ -20,7 +20,8 @@ const defaultBaseURL = "https://api.liquidweb.com"
// Environment variables names.
const (
envNamespace = "LIQUID_WEB_"
envNamespace = "LIQUID_WEB_"
altEnvNamespace = "LWAPI_"
EnvURL = envNamespace + "URL"
EnvUsername = envNamespace + "USERNAME"
@ -49,10 +50,10 @@ type Config struct {
func NewDefaultConfig() *Config {
return &Config{
BaseURL: defaultBaseURL,
TTL: env.GetOrDefaultInt(EnvTTL, 300),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 1*time.Minute),
TTL: env.GetOneWithFallback(EnvTTL, 300, strconv.Atoi, altEnvName(EnvTTL)),
PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, 2*time.Minute, env.ParseSecond, altEnvName(EnvPropagationTimeout)),
PollingInterval: env.GetOneWithFallback(EnvPollingInterval, 2*time.Second, env.ParseSecond, altEnvName(EnvPollingInterval)),
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.
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 {
return nil, fmt.Errorf("liquidweb: %w", err)
}
config := NewDefaultConfig()
config.BaseURL = env.GetOrFile(EnvURL)
config.BaseURL = env.GetOneWithFallback(EnvURL, defaultBaseURL, env.ParseString, altEnvName(EnvURL))
config.Username = values[EnvUsername]
config.Password = values[EnvPassword]
config.Zone = env.GetOrDefaultString(EnvZone, "")
config.Zone = env.GetOneWithFallback(EnvZone, "", env.ParseString, altEnvName(EnvZone))
return NewDNSProviderConfig(config)
}
@ -191,3 +195,7 @@ func (d *DNSProvider) findZone(domain string) (string, error) {
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"
Example = '''
LIQUID_WEB_USERNAME=someuser \
LIQUID_WEB_PASSWORD=somepass \
LWAPI_USERNAME=someuser \
LWAPI_PASSWORD=somepass \
lego --email you@example.com --dns liquidweb --domains my.example.org run
'''
[Configuration]
[Configuration.Credentials]
LIQUID_WEB_USERNAME = "Liquid Web API Username"
LIQUID_WEB_PASSWORD = "Liquid Web API Password"
LWAPI_USERNAME = "Liquid Web API Username"
LWAPI_PASSWORD = "Liquid Web API Password"
[Configuration.Additional]
LIQUID_WEB_ZONE = "DNS Zone"
LIQUID_WEB_URL = "Liquid Web API endpoint"
LIQUID_WEB_TTL = "The TTL of the TXT record used for the DNS challenge"
LIQUID_WEB_POLLING_INTERVAL = "Time between DNS propagation check"
LIQUID_WEB_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_ZONE = "DNS Zone"
LWAPI_URL = "Liquid Web API endpoint"
LWAPI_TTL = "The TTL of the TXT record used for the DNS challenge"
LWAPI_POLLING_INTERVAL = "Time between DNS propagation check"
LWAPI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
LWAPI_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)"
[Links]
API = "https://api.liquidweb.com/docs/"