-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
187 additions
and
12 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 |
---|---|---|
@@ -1,25 +1,68 @@ | ||
package config | ||
|
||
import "github.com/rs/zerolog" | ||
import ( | ||
"fmt" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/kelseyhightower/envconfig" | ||
"github.com/rotationalio/ensign/pkg/utils/logger" | ||
"github.com/rs/zerolog" | ||
) | ||
|
||
// Config uses envconfig to load the required settings from the environment, parse and | ||
// validate them, loading defaults where necessary in preparation for running the | ||
// Quarterdeck API service. This is the top-level config, all sub configurations need | ||
// to be defined as properties of this Config. | ||
type Config struct { | ||
Maintenance bool `split_words:"true" default:"false"` | ||
BindAddr string `split_words:"true" default:"8088"` | ||
Mode string `split_words:"true" default:"release"` | ||
LogLevel string `split_words:"true" default:"info"` | ||
ConsoleLog bool `split_words:"true" default:"false"` | ||
Maintenance bool `default:"false"` // $QUARTERDECK_MAINTENANCE | ||
BindAddr string `split_words:"true" default:":8088"` // $QUARTERDECK_BIND_ADDR | ||
Mode string `default:"release"` // $QUARTERDECK_MODE | ||
LogLevel logger.LevelDecoder `split_words:"true" default:"info"` // $QUARTERDECK_LOG_LEVEL | ||
ConsoleLog bool `split_words:"true" default:"false"` // $QUARTERDECK_CONSOLE_LOG | ||
processed bool // set when the config is properly procesesed from the environment | ||
} | ||
|
||
func New() (Config, error) { | ||
return Config{}, nil | ||
// New loads and parses the config from the environment and validates it, marking it as | ||
// processed so that external users can determine if the config is ready for use. This | ||
// should be the only way Config objects are created for use in the application. | ||
func New() (conf Config, err error) { | ||
if err = envconfig.Process("quarterdeck", &conf); err != nil { | ||
return Config{}, err | ||
} | ||
|
||
if err = conf.Validate(); err != nil { | ||
return Config{}, err | ||
} | ||
|
||
conf.processed = true | ||
return conf, nil | ||
} | ||
|
||
// Returns true if the config has not been correctly processed from the environment. | ||
func (c Config) IsZero() bool { | ||
// TODO: implement | ||
return false | ||
return !c.processed | ||
} | ||
|
||
// Mark a manually constructed config as processed as long as it is valid. | ||
func (c Config) Mark() (_ Config, err error) { | ||
if err = c.Validate(); err != nil { | ||
return c, err | ||
} | ||
c.processed = true | ||
return c, nil | ||
} | ||
|
||
// Custom validations are added here, particularly validations that require one or more | ||
// fields to be processed before the validation occurs. | ||
// NOTE: ensure that all nested config validation methods are called here. | ||
func (c Config) Validate() (err error) { | ||
if c.Mode != gin.ReleaseMode && c.Mode != gin.DebugMode && c.Mode != gin.TestMode { | ||
return fmt.Errorf("invalid configuration: %q is not a valid gin mode", c.Mode) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c Config) GetLogLevel() zerolog.Level { | ||
// TODO: implement | ||
return zerolog.InfoLevel | ||
return zerolog.Level(c.LogLevel) | ||
} |
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,132 @@ | ||
package config_test | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/rotationalio/ensign/pkg/quarterdeck/config" | ||
"github.com/rotationalio/ensign/pkg/utils/logger" | ||
"github.com/rs/zerolog" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// The test environment for all config tests, manipulated using curEnv and setEnv | ||
var testEnv = map[string]string{ | ||
"QUARTERDECK_MAINTENANCE": "false", | ||
"QUARTERDECK_BIND_ADDR": ":3636", | ||
"QUARTERDECK_MODE": gin.TestMode, | ||
"QUARTERDECK_LOG_LEVEL": "error", | ||
"QUARTERDECK_CONSOLE_LOG": "true", | ||
} | ||
|
||
func TestConfig(t *testing.T) { | ||
// Set the required environment variables and cleanup after. | ||
prevEnv := curEnv() | ||
t.Cleanup(func() { | ||
for key, val := range prevEnv { | ||
if val != "" { | ||
os.Setenv(key, val) | ||
} else { | ||
os.Unsetenv(key) | ||
} | ||
} | ||
}) | ||
setEnv() | ||
|
||
// At this point in the test, the environment should contain testEnv | ||
conf, err := config.New() | ||
require.NoError(t, err, "could not create a default config") | ||
require.False(t, conf.IsZero(), "default config should be processed") | ||
|
||
// Test the configuration | ||
require.False(t, conf.Maintenance) | ||
require.Equal(t, testEnv["QUARTERDECK_BIND_ADDR"], conf.BindAddr) | ||
require.Equal(t, testEnv["QUARTERDECK_MODE"], conf.Mode) | ||
require.Equal(t, zerolog.ErrorLevel, conf.GetLogLevel()) | ||
require.True(t, conf.ConsoleLog) | ||
} | ||
|
||
func TestValidation(t *testing.T) { | ||
conf, err := config.New() | ||
require.NoError(t, err, "could not create default config") | ||
|
||
modes := []string{gin.ReleaseMode, gin.DebugMode, gin.TestMode} | ||
for _, mode := range modes { | ||
conf.Mode = mode | ||
require.NoError(t, conf.Validate(), "expected config to be valid in %q mode", mode) | ||
} | ||
|
||
// Ensure conf is invalid on wrong mode | ||
conf.Mode = "invalid" | ||
require.EqualError(t, conf.Validate(), `invalid configuration: "invalid" is not a valid gin mode`, "expected gin mode validation error") | ||
} | ||
|
||
func TestIsZero(t *testing.T) { | ||
// An empty config should always return IsZero | ||
require.True(t, config.Config{}.IsZero(), "an empty config should always be zero valued") | ||
|
||
// A processed config should not be zero valued | ||
conf, err := config.New() | ||
require.NoError(t, err, "should have been able to load the config") | ||
require.False(t, conf.IsZero(), "expected a processed config to be non-zero valued") | ||
|
||
// Custom config not processed | ||
conf = config.Config{ | ||
Maintenance: false, | ||
BindAddr: "127.0.0.1:0", | ||
LogLevel: logger.LevelDecoder(zerolog.TraceLevel), | ||
Mode: "invalid", | ||
} | ||
require.True(t, config.Config{}.IsZero(), "a non-empty config that isn't marked will be zero valued") | ||
|
||
// Should not be able to mark a custom config that is invalid | ||
conf, err = conf.Mark() | ||
require.EqualError(t, err, `invalid configuration: "invalid" is not a valid gin mode`, "expected gin mode validation error") | ||
|
||
// Should be able to mark a custom config that is valid as processed | ||
conf.Mode = gin.ReleaseMode | ||
conf, err = conf.Mark() | ||
require.NoError(t, err, "should be able to mark a valid config") | ||
require.False(t, conf.IsZero(), "a marked config should not be zero-valued") | ||
} | ||
|
||
// Returns the current environment for the specified keys, or if no keys are specified | ||
// then returns the current environment for all keys in testEnv. | ||
func curEnv(keys ...string) map[string]string { | ||
env := make(map[string]string) | ||
|
||
if len(keys) > 0 { | ||
// Process the keys passed in by the user | ||
for _, key := range keys { | ||
if val, ok := os.LookupEnv(key); ok { | ||
env[key] = val | ||
} | ||
} | ||
} else { | ||
// Process all the keys in testEnv | ||
for key := range testEnv { | ||
if val, ok := os.LookupEnv(key); ok { | ||
env[key] = val | ||
} | ||
} | ||
} | ||
|
||
return env | ||
} | ||
|
||
// Sets the environment variables from the testEnv, if no keys are specified then sets | ||
// all environment variables that are specified in the testEnv. | ||
func setEnv(keys ...string) { | ||
if len(keys) > 0 { | ||
for _, key := range keys { | ||
if val, ok := testEnv[key]; ok { | ||
os.Setenv(key, val) | ||
} | ||
} | ||
} else { | ||
for key, val := range testEnv { | ||
os.Setenv(key, val) | ||
} | ||
} | ||
} |