Adds documentation for the configuration parser and tests

This commit is contained in:
Brian Bland 2014-10-29 17:42:23 -07:00
parent 0ad4bba103
commit 2013ef5a5b
2 changed files with 83 additions and 7 deletions

View file

@ -10,13 +10,19 @@ import (
"gopkg.in/yaml.v2"
)
// CurrentVersion is the most recent Version that can be parsed
var CurrentVersion = Version{Major: 0, Minor: 1}
// Configuration is a versioned system configuration
// When marshaled into yaml, this produces a document matching the current version's format
type Configuration struct {
Version Version `yaml:"version"`
Registry Registry `yaml:"registry"`
}
// Version is a major/minor version pair
// Minor version upgrades should be strictly additive
// Major version upgrades indicate structure or type changes
type Version struct {
Major uint
Minor uint
@ -26,17 +32,26 @@ func (version Version) String() string {
return fmt.Sprintf("%d.%d", version.Major, version.Minor)
}
// MarshalYAML is implemented to serialize the Version into a string format
func (version Version) MarshalYAML() (interface{}, error) {
return version.String(), nil
}
// Registry defines the configuration for a registry
type Registry struct {
// LogLevel specifies the level at which the registry will be logged
LogLevel string
Storage Storage
// Storage specifies the configuration of the registry's object storage
Storage Storage
}
// Storage defines the configuration for registry object storage
type Storage struct {
Type string
// Type specifies the storage driver type (examples: inmemory, filesystem, s3, ...)
Type string
// Parameters specifies the key/value parameters map passed to the storage driver constructor
Parameters map[string]string
}
@ -44,16 +59,44 @@ func (storage Storage) MarshalYAML() (interface{}, error) {
return yaml.MapSlice{yaml.MapItem{storage.Type, storage.Parameters}}, nil
}
// untypedConfiguration is the unmarshalable configuration struct that only assumes the existence of
// a version string parameter
// This is done to parse the configuration version, then parse the remainder with a version-specific
// parser
type untypedConfiguration struct {
Version string `yaml:"version"`
// Version is the version string defined in a configuration yaml
// This can safely parse versions defined as float types in yaml
Version string `yaml:"version"`
// Registry is an untyped placeholder for the Registry configuration, which can later be parsed
// into a current Registry struct
Registry interface{} `yaml:"registry"`
}
type v_0_1_RegistryConfiguration struct {
LogLevel string `yaml:"loglevel"`
Storage interface{} `yaml:"storage"`
// V_0_1_RegistryConfiguration is the unmarshalable Registry configuration struct specific to
// Version{0, 1}
type V_0_1_RegistryConfiguration struct {
// LogLevel is the level at which the registry will log
// The loglevel can be overridden with the environment variable REGISTRY_LOGLEVEL, for example:
// REGISTRY_LOGLEVEL=info
LogLevel string `yaml:"loglevel"`
// Storage is an untyped placeholder for the Storage configuration, which can later be parsed as
// a Storage struct
// The storage type can be overridden with the environment variable REGISTRY_STORAGE, for
// example: REGISTRY_STORAGE=s3
// Note: If REGISTRY_STORAGE changes the storage type, all included parameters will be ignored
// The storage parameters can be overridden with any environment variable of the format:
// REGISTRY_STORAGE_<storage driver type>_<parameter name>, for example:
// REGISTRY_STORAGE_S3_BUCKET=my-bucket
Storage interface{} `yaml:"storage"`
}
// Parse parses an input configuration yaml document into a Configuration struct
// This should be capable of handling old configuration format versions
//
// Environment variables may be used to override configuration parameters other than version, which
// may be defined on a per-version basis. See V_0_1_RegistryConfiguration for more details
func Parse(in []byte) (*Configuration, error) {
var untypedConfig untypedConfiguration
var config Configuration
@ -65,6 +108,8 @@ func Parse(in []byte) (*Configuration, error) {
if untypedConfig.Version == "" {
return nil, fmt.Errorf("Please specify a configuration version. Current version is %s", CurrentVersion)
}
// Convert the version string from X.Y to Version{X, Y}
versionParts := strings.Split(untypedConfig.Version, ".")
if len(versionParts) != 2 {
return nil, fmt.Errorf("Invalid version: %s Expected format: X.Y", untypedConfig.Version)
@ -79,6 +124,7 @@ func Parse(in []byte) (*Configuration, error) {
}
config.Version = Version{Major: uint(majorVersion), Minor: uint(minorVersion)}
// Parse the remainder of the configuration depending on the provided version
switch config.Version {
case Version{0, 1}:
registry, err := parseV_0_1_Registry(untypedConfig.Registry)
@ -100,6 +146,7 @@ func Parse(in []byte) (*Configuration, error) {
return &config, nil
}
// parseV_0_1_Registry parses a Registry configuration for Version{0, 1}
func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
envMap := getEnvMap()
@ -107,7 +154,7 @@ func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
if err != nil {
return nil, err
}
var v_0_1 v_0_1_RegistryConfiguration
var v_0_1 V_0_1_RegistryConfiguration
err = yaml.Unmarshal(registryBytes, &v_0_1)
if err != nil {
return nil, err
@ -123,8 +170,10 @@ func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
switch v_0_1.Storage.(type) {
case string:
// Storage is provided only by type
storage.Type = v_0_1.Storage.(string)
case map[interface{}]interface{}:
// Storage is provided as a {type: parameters} map
storageMap := v_0_1.Storage.(map[interface{}]interface{})
if len(storageMap) > 1 {
keys := make([]string, 0, len(storageMap))
@ -136,6 +185,8 @@ func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
var params map[interface{}]interface{}
// There will only be one key-value pair at this point
for k, v := range storageMap {
// Parameters may be parsed as numerical or boolean values, so just convert these to
// strings
storage.Type = toString(k)
paramsMap, ok := v.(map[interface{}]interface{})
if !ok {
@ -164,6 +215,8 @@ func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
return nil, fmt.Errorf("Must provide exactly one storage type, optionally with parameters. Provided: %v", v_0_1.Storage)
}
// Find all environment variables of the format:
// REGISTRY_STORAGE_<storage driver type>_<parameter name>
storageParamsRegexp, err := regexp.Compile(fmt.Sprintf("^REGISTRY_STORAGE_%s_([A-Z0-9]+)$", strings.ToUpper(storage.Type)))
if err != nil {
return nil, err
@ -177,6 +230,7 @@ func parseV_0_1_Registry(registry interface{}) (*Registry, error) {
return &Registry{LogLevel: v_0_1.LogLevel, Storage: storage}, nil
}
// getEnvMap reads the current environment variables and converts these into a key/value map
func getEnvMap() map[string]string {
envMap := make(map[string]string)
for _, env := range os.Environ() {
@ -186,6 +240,7 @@ func getEnvMap() map[string]string {
return envMap
}
// toString converts reasonable objects into strings that may be used for configuration parameters
func toString(v interface{}) string {
if v == nil {
return ""