forked from eduardolat/pgbackweb
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add config package with environment validation and logging utilities
- Loading branch information
1 parent
4c10c74
commit c7ba844
Showing
6 changed files
with
404 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Encryption key is used to encrypt and decrypt the sensitive data stored | ||
# in the database such as database credentials, secret keys, etc. | ||
PBW_ENCRYPTION_KEY="" | ||
|
||
# Database connection string for a PostgreSQL database where the pgbackweb | ||
# will store its data. | ||
PBW_POSTGRES_CONN_STRING="" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package config | ||
|
||
import ( | ||
"github.com/joho/godotenv" | ||
) | ||
|
||
type Env struct { | ||
PBW_ENCRYPTION_KEY *string | ||
PBW_POSTGRES_CONN_STRING *string | ||
} | ||
|
||
// GetEnv returns the environment variables. | ||
// | ||
// If there is an error, it will log it and exit the program. | ||
func GetEnv() *Env { | ||
err := godotenv.Load() | ||
if err == nil { | ||
logInfo("using .env file") | ||
} | ||
|
||
env := &Env{ | ||
PBW_ENCRYPTION_KEY: getEnvAsString(getEnvAsStringParams{ | ||
name: "PBW_ENCRYPTION_KEY", | ||
isRequired: true, | ||
}), | ||
PBW_POSTGRES_CONN_STRING: getEnvAsString(getEnvAsStringParams{ | ||
name: "PBW_POSTGRES_CONN_STRING", | ||
isRequired: true, | ||
}), | ||
} | ||
|
||
validateEnv(env) | ||
return env | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package config | ||
|
||
// validateEnv runs additional validations on the environment variables. | ||
func validateEnv(env *Env) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package config | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"strconv" | ||
) | ||
|
||
type getEnvAsStringParams struct { | ||
name string | ||
defaultValue *string | ||
isRequired bool | ||
} | ||
|
||
// defaultValue returns a pointer to the given value. | ||
func newDefaultValue[T any](value T) *T { | ||
return &value | ||
} | ||
|
||
// getEnvAsString returns the value of the environment variable with the given name. | ||
func getEnvAsString(params getEnvAsStringParams) *string { //nolint:all | ||
value, err := getEnvAsStringFunc(params) | ||
|
||
if err != nil { | ||
logFatalError( | ||
"error getting env variable", | ||
"name", params.name, | ||
"error", err, | ||
) | ||
} | ||
|
||
return value | ||
} | ||
|
||
// getEnvAsStringFunc is the outlying function for getEnvAsString. | ||
func getEnvAsStringFunc(params getEnvAsStringParams) (*string, error) { | ||
if params.defaultValue != nil && params.isRequired { | ||
return nil, errors.New("cannot have both a default value and be required") | ||
} | ||
|
||
value, exists := os.LookupEnv(params.name) | ||
|
||
if !exists && params.isRequired { | ||
return nil, errors.New("required env variable does not exist") | ||
} | ||
|
||
if !exists { | ||
if params.defaultValue != nil { | ||
return params.defaultValue, nil | ||
} | ||
return nil, nil | ||
} | ||
|
||
return &value, nil | ||
} | ||
|
||
type getEnvAsIntParams struct { | ||
name string | ||
defaultValue *int | ||
isRequired bool | ||
} | ||
|
||
// getEnvAsInt returns the value of the environment variable with the given name. | ||
func getEnvAsInt(params getEnvAsIntParams) *int { //nolint:all | ||
value, err := getEnvAsIntFunc(params) | ||
|
||
if err != nil { | ||
logFatalError( | ||
"error getting env variable", | ||
"name", params.name, | ||
"error", err, | ||
) | ||
} | ||
|
||
return value | ||
} | ||
|
||
// getEnvAsIntFunc is the outlying function for getEnvAsInt. | ||
func getEnvAsIntFunc(params getEnvAsIntParams) (*int, error) { | ||
if params.defaultValue != nil && params.isRequired { | ||
return nil, errors.New("cannot have both a default value and be required") | ||
} | ||
|
||
valueStr, exists := os.LookupEnv(params.name) | ||
|
||
if !exists && params.isRequired { | ||
return nil, errors.New("required env variable does not exist") | ||
} | ||
|
||
if !exists { | ||
if params.defaultValue != nil { | ||
return params.defaultValue, nil | ||
} | ||
return nil, nil | ||
} | ||
|
||
value, err := strconv.Atoi(valueStr) | ||
|
||
if err != nil { | ||
return nil, errors.New("env variable is not an integer") | ||
} | ||
|
||
return &value, nil | ||
} | ||
|
||
type getEnvAsBoolParams struct { | ||
name string | ||
defaultValue *bool | ||
isRequired bool | ||
} | ||
|
||
// getEnvAsBool returns the value of the environment variable with the given name. | ||
func getEnvAsBool(params getEnvAsBoolParams) *bool { //nolint:all | ||
value, err := getEnvAsBoolFunc(params) | ||
|
||
if err != nil { | ||
logFatalError( | ||
"error getting env variable", | ||
"name", params.name, | ||
"error", err, | ||
) | ||
} | ||
|
||
return value | ||
} | ||
|
||
// getEnvAsBoolFunc is the outlying function for getEnvAsBool. | ||
func getEnvAsBoolFunc(params getEnvAsBoolParams) (*bool, error) { | ||
if params.defaultValue != nil && params.isRequired { | ||
return nil, errors.New("cannot have both a default value and be required") | ||
} | ||
|
||
valueStr, exists := os.LookupEnv(params.name) | ||
|
||
if !exists && params.isRequired { | ||
return nil, errors.New("required env variable does not exist") | ||
} | ||
|
||
if !exists { | ||
if params.defaultValue != nil { | ||
return params.defaultValue, nil | ||
} | ||
f := false | ||
return &f, nil | ||
} | ||
|
||
value, err := strconv.ParseBool(valueStr) | ||
|
||
if err != nil { | ||
return nil, errors.New("env variable is not a boolean, must be true or false") | ||
} | ||
|
||
return &value, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package config | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestGetEnvAsStringFunc(t *testing.T) { | ||
// Test when environment variable exists | ||
os.Setenv("TEST_ENV", "test_value") | ||
value, err := getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "TEST_ENV", | ||
isRequired: true, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "test_value", *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable does not exist, default value is provided, and is not required | ||
value, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "NON_EXISTENT_ENV", | ||
defaultValue: newDefaultValue("default_value"), | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "default_value", *value) | ||
|
||
// Test when environment variable does not exist, no default value is provided, and is required | ||
// This should return an error | ||
value, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "NON_EXISTENT_ENV", | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
assert.Nil(t, value) | ||
|
||
// Test when environment variable exists, default value is provided, and is required | ||
os.Setenv("TEST_ENV", "test_value") | ||
value, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "TEST_ENV", | ||
defaultValue: newDefaultValue("default_value"), | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "test_value", *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable exists, is not required, and no default value is provided | ||
os.Setenv("TEST_ENV", "test_value") | ||
value, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "TEST_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "test_value", *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable does not exist, is not required, and no default value is provided | ||
value, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "NON_EXISTENT_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Nil(t, value) | ||
|
||
// Test when default value and required are both present | ||
// This should return an error | ||
_, err = getEnvAsStringFunc(getEnvAsStringParams{ | ||
name: "NON_EXISTENT_ENV", | ||
defaultValue: newDefaultValue("default_value"), | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestGetEnvAsIntFunc(t *testing.T) { | ||
// Test when environment variable exists and is an integer | ||
os.Setenv("TEST_ENV", "123") | ||
value, err := getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "TEST_ENV", | ||
isRequired: true, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 123, *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable does not exist, default value is provided, and is not required | ||
value, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "NON_EXISTENT_ENV", | ||
defaultValue: newDefaultValue(456), | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 456, *value) | ||
|
||
// Test when environment variable does not exist, no default value is provided, and is required | ||
// This should return an error | ||
value, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "NON_EXISTENT_ENV", | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
assert.Nil(t, value) | ||
|
||
// Test when environment variable exists, is not an integer, no default value is provided, and is required | ||
// This should return an error | ||
os.Setenv("TEST_ENV", "not_an_integer") | ||
value, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "TEST_ENV", | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
assert.Nil(t, value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable exists, is not required, and no default value is provided | ||
os.Setenv("TEST_ENV", "123") | ||
value, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "TEST_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 123, *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable does not exist, is not required, and no default value is provided | ||
value, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "NON_EXISTENT_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Nil(t, value) | ||
|
||
// Test when default value and required are both present | ||
// This should return an error | ||
_, err = getEnvAsIntFunc(getEnvAsIntParams{ | ||
name: "NON_EXISTENT_ENV", | ||
defaultValue: newDefaultValue(1), | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestGetEnvAsBoolFunc(t *testing.T) { | ||
// Test when environment variable exists and is a boolean | ||
os.Setenv("TEST_ENV", "true") | ||
value, err := getEnvAsBoolFunc(getEnvAsBoolParams{ | ||
name: "TEST_ENV", | ||
isRequired: true, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, true, *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable exists, is not a boolean, and is required | ||
os.Setenv("TEST_ENV", "not_a_boolean") | ||
_, err = getEnvAsBoolFunc(getEnvAsBoolParams{ | ||
name: "TEST_ENV", | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable exists, is not required, and no default value is provided | ||
os.Setenv("TEST_ENV", "true") | ||
value, err = getEnvAsBoolFunc(getEnvAsBoolParams{ | ||
name: "TEST_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, true, *value) | ||
os.Unsetenv("TEST_ENV") | ||
|
||
// Test when environment variable does not exist, is not required, and no default value is provided | ||
value, err = getEnvAsBoolFunc(getEnvAsBoolParams{ | ||
name: "NON_EXISTENT_ENV", | ||
isRequired: false, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, false, *value) | ||
|
||
// Test when default value and required are both present | ||
// This should return an error | ||
_, err = getEnvAsBoolFunc(getEnvAsBoolParams{ | ||
name: "NON_EXISTENT_ENV", | ||
defaultValue: newDefaultValue(true), | ||
isRequired: true, | ||
}) | ||
assert.Error(t, err) | ||
} |
Oops, something went wrong.