Initial configuration parser
							parent
							
								
									df7eed3a2c
								
							
						
					
					
						commit
						0ad4bba103
					
				|  | @ -0,0 +1,194 @@ | |||
| package configuration | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
| 
 | ||||
| var CurrentVersion = Version{Major: 0, Minor: 1} | ||||
| 
 | ||||
| type Configuration struct { | ||||
| 	Version  Version  `yaml:"version"` | ||||
| 	Registry Registry `yaml:"registry"` | ||||
| } | ||||
| 
 | ||||
| type Version struct { | ||||
| 	Major uint | ||||
| 	Minor uint | ||||
| } | ||||
| 
 | ||||
| func (version Version) String() string { | ||||
| 	return fmt.Sprintf("%d.%d", version.Major, version.Minor) | ||||
| } | ||||
| 
 | ||||
| func (version Version) MarshalYAML() (interface{}, error) { | ||||
| 	return version.String(), nil | ||||
| } | ||||
| 
 | ||||
| type Registry struct { | ||||
| 	LogLevel string | ||||
| 	Storage  Storage | ||||
| } | ||||
| 
 | ||||
| type Storage struct { | ||||
| 	Type       string | ||||
| 	Parameters map[string]string | ||||
| } | ||||
| 
 | ||||
| func (storage Storage) MarshalYAML() (interface{}, error) { | ||||
| 	return yaml.MapSlice{yaml.MapItem{storage.Type, storage.Parameters}}, nil | ||||
| } | ||||
| 
 | ||||
| type untypedConfiguration struct { | ||||
| 	Version  string      `yaml:"version"` | ||||
| 	Registry interface{} `yaml:"registry"` | ||||
| } | ||||
| 
 | ||||
| type v_0_1_RegistryConfiguration struct { | ||||
| 	LogLevel string      `yaml:"loglevel"` | ||||
| 	Storage  interface{} `yaml:"storage"` | ||||
| } | ||||
| 
 | ||||
| func Parse(in []byte) (*Configuration, error) { | ||||
| 	var untypedConfig untypedConfiguration | ||||
| 	var config Configuration | ||||
| 
 | ||||
| 	err := yaml.Unmarshal(in, &untypedConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if untypedConfig.Version == "" { | ||||
| 		return nil, fmt.Errorf("Please specify a configuration version. Current version is %s", CurrentVersion) | ||||
| 	} | ||||
| 	versionParts := strings.Split(untypedConfig.Version, ".") | ||||
| 	if len(versionParts) != 2 { | ||||
| 		return nil, fmt.Errorf("Invalid version: %s Expected format: X.Y", untypedConfig.Version) | ||||
| 	} | ||||
| 	majorVersion, err := strconv.ParseUint(versionParts[0], 10, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Major version must be of type uint, received %v", versionParts[0]) | ||||
| 	} | ||||
| 	minorVersion, err := strconv.ParseUint(versionParts[1], 10, 0) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Minor version must be of type uint, received %v", versionParts[1]) | ||||
| 	} | ||||
| 	config.Version = Version{Major: uint(majorVersion), Minor: uint(minorVersion)} | ||||
| 
 | ||||
| 	switch config.Version { | ||||
| 	case Version{0, 1}: | ||||
| 		registry, err := parseV_0_1_Registry(untypedConfig.Registry) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		config.Registry = *registry | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Unsupported configuration version %s Current version is %s", config.Version, CurrentVersion) | ||||
| 	} | ||||
| 
 | ||||
| 	switch config.Registry.LogLevel { | ||||
| 	case "error", "warn", "info", "debug": | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("Invalid loglevel %s Must be one of [error, warn, info, debug]", config.Registry.LogLevel) | ||||
| 	} | ||||
| 
 | ||||
| 	return &config, nil | ||||
| } | ||||
| 
 | ||||
| func parseV_0_1_Registry(registry interface{}) (*Registry, error) { | ||||
| 	envMap := getEnvMap() | ||||
| 
 | ||||
| 	registryBytes, err := yaml.Marshal(registry) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var v_0_1 v_0_1_RegistryConfiguration | ||||
| 	err = yaml.Unmarshal(registryBytes, &v_0_1) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if logLevel, ok := envMap["REGISTRY_LOGLEVEL"]; ok { | ||||
| 		v_0_1.LogLevel = logLevel | ||||
| 	} | ||||
| 	v_0_1.LogLevel = strings.ToLower(v_0_1.LogLevel) | ||||
| 
 | ||||
| 	var storage Storage | ||||
| 	storage.Parameters = make(map[string]string) | ||||
| 
 | ||||
| 	switch v_0_1.Storage.(type) { | ||||
| 	case string: | ||||
| 		storage.Type = v_0_1.Storage.(string) | ||||
| 	case map[interface{}]interface{}: | ||||
| 		storageMap := v_0_1.Storage.(map[interface{}]interface{}) | ||||
| 		if len(storageMap) > 1 { | ||||
| 			keys := make([]string, 0, len(storageMap)) | ||||
| 			for key := range storageMap { | ||||
| 				keys = append(keys, toString(key)) | ||||
| 			} | ||||
| 			return nil, fmt.Errorf("Must provide exactly one storage type. Provided: %v", keys) | ||||
| 		} | ||||
| 		var params map[interface{}]interface{} | ||||
| 		// There will only be one key-value pair at this point
 | ||||
| 		for k, v := range storageMap { | ||||
| 			storage.Type = toString(k) | ||||
| 			paramsMap, ok := v.(map[interface{}]interface{}) | ||||
| 			if !ok { | ||||
| 				return nil, fmt.Errorf("Must provide parameters as a map[string]string. Provided: %#v", v) | ||||
| 			} | ||||
| 			params = paramsMap | ||||
| 		} | ||||
| 		for k, v := range params { | ||||
| 			storage.Parameters[toString(k)] = toString(v) | ||||
| 		} | ||||
| 
 | ||||
| 	case interface{}: | ||||
| 		// Bad type for storage
 | ||||
| 		return nil, fmt.Errorf("Registry storage must be provided by name, optionally with parameters. Provided: %v", v_0_1.Storage) | ||||
| 	} | ||||
| 
 | ||||
| 	if storageType, ok := envMap["REGISTRY_STORAGE"]; ok { | ||||
| 		if storageType != storage.Type { | ||||
| 			storage.Type = storageType | ||||
| 			// Reset the storage parameters because we're using a different storage type
 | ||||
| 			storage.Parameters = make(map[string]string) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if storage.Type == "" { | ||||
| 		return nil, fmt.Errorf("Must provide exactly one storage type, optionally with parameters. Provided: %v", v_0_1.Storage) | ||||
| 	} | ||||
| 
 | ||||
| 	storageParamsRegexp, err := regexp.Compile(fmt.Sprintf("^REGISTRY_STORAGE_%s_([A-Z0-9]+)$", strings.ToUpper(storage.Type))) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	for k, v := range envMap { | ||||
| 		if submatches := storageParamsRegexp.FindStringSubmatch(k); submatches != nil { | ||||
| 			storage.Parameters[strings.ToLower(submatches[1])] = v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return &Registry{LogLevel: v_0_1.LogLevel, Storage: storage}, nil | ||||
| } | ||||
| 
 | ||||
| func getEnvMap() map[string]string { | ||||
| 	envMap := make(map[string]string) | ||||
| 	for _, env := range os.Environ() { | ||||
| 		envParts := strings.SplitN(env, "=", 2) | ||||
| 		envMap[envParts[0]] = envParts[1] | ||||
| 	} | ||||
| 	return envMap | ||||
| } | ||||
| 
 | ||||
| func toString(v interface{}) string { | ||||
| 	if v == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return fmt.Sprint(v) | ||||
| } | ||||
|  | @ -0,0 +1,171 @@ | |||
| package configuration | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 
 | ||||
| 	. "gopkg.in/check.v1" | ||||
| ) | ||||
| 
 | ||||
| // Hook up gocheck into the "go test" runner
 | ||||
| func Test(t *testing.T) { TestingT(t) } | ||||
| 
 | ||||
| var configStruct = Configuration{ | ||||
| 	Version: Version{ | ||||
| 		Major: 0, | ||||
| 		Minor: 1, | ||||
| 	}, | ||||
| 	Registry: Registry{ | ||||
| 		LogLevel: "info", | ||||
| 		Storage: Storage{ | ||||
| 			Type: "s3", | ||||
| 			Parameters: map[string]string{ | ||||
| 				"region":    "us-east-1", | ||||
| 				"bucket":    "my-bucket", | ||||
| 				"rootpath":  "/registry", | ||||
| 				"encrypt":   "true", | ||||
| 				"secure":    "false", | ||||
| 				"accesskey": "SAMPLEACCESSKEY", | ||||
| 				"secretkey": "SUPERSECRET", | ||||
| 				"host":      "", | ||||
| 				"port":      "", | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var configYamlV_0_1 = ` | ||||
| version: 0.1 | ||||
| 
 | ||||
| registry: | ||||
|   loglevel: info | ||||
|   storage: | ||||
|     s3: | ||||
|       region: us-east-1 | ||||
|       bucket: my-bucket | ||||
|       rootpath: /registry | ||||
|       encrypt: true | ||||
|       secure: false | ||||
|       accesskey: SAMPLEACCESSKEY | ||||
|       secretkey: SUPERSECRET | ||||
|       host: ~ | ||||
|       port: ~ | ||||
| ` | ||||
| 
 | ||||
| type ConfigSuite struct { | ||||
| 	expectedConfig *Configuration | ||||
| } | ||||
| 
 | ||||
| var _ = Suite(new(ConfigSuite)) | ||||
| 
 | ||||
| func (suite *ConfigSuite) SetUpTest(c *C) { | ||||
| 	os.Clearenv() | ||||
| 	suite.expectedConfig = copyConfig(configStruct) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestMarshalRoundtrip(c *C) { | ||||
| 	configBytes, err := yaml.Marshal(suite.expectedConfig) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	config, err := Parse(configBytes) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseSimple(c *C) { | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithSameEnvStorage(c *C) { | ||||
| 	os.Setenv("REGISTRY_STORAGE", "s3") | ||||
| 	os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-east-1") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithDifferentEnvStorageParams(c *C) { | ||||
| 	suite.expectedConfig.Registry.Storage.Parameters["region"] = "us-west-1" | ||||
| 	suite.expectedConfig.Registry.Storage.Parameters["secure"] = "true" | ||||
| 	suite.expectedConfig.Registry.Storage.Parameters["newparam"] = "some Value" | ||||
| 
 | ||||
| 	os.Setenv("REGISTRY_STORAGE_S3_REGION", "us-west-1") | ||||
| 	os.Setenv("REGISTRY_STORAGE_S3_SECURE", "true") | ||||
| 	os.Setenv("REGISTRY_STORAGE_S3_NEWPARAM", "some Value") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithDifferentEnvStorageType(c *C) { | ||||
| 	suite.expectedConfig.Registry.Storage = Storage{Type: "inmemory", Parameters: map[string]string{}} | ||||
| 
 | ||||
| 	os.Setenv("REGISTRY_STORAGE", "inmemory") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithDifferentEnvStorageTypeAndParams(c *C) { | ||||
| 	suite.expectedConfig.Registry.Storage = Storage{Type: "filesystem", Parameters: map[string]string{}} | ||||
| 	suite.expectedConfig.Registry.Storage.Parameters["rootdirectory"] = "/tmp/testroot" | ||||
| 
 | ||||
| 	os.Setenv("REGISTRY_STORAGE", "filesystem") | ||||
| 	os.Setenv("REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY", "/tmp/testroot") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithSameEnvLoglevel(c *C) { | ||||
| 	os.Setenv("REGISTRY_LOGLEVEL", "info") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *C) { | ||||
| 	suite.expectedConfig.Registry.LogLevel = "error" | ||||
| 
 | ||||
| 	os.Setenv("REGISTRY_LOGLEVEL", "error") | ||||
| 
 | ||||
| 	config, err := Parse([]byte(configYamlV_0_1)) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	c.Assert(config, DeepEquals, suite.expectedConfig) | ||||
| } | ||||
| 
 | ||||
| func (suite *ConfigSuite) TestParseInvalidVersion(c *C) { | ||||
| 	suite.expectedConfig.Version = Version{Major: CurrentVersion.Major, Minor: CurrentVersion.Minor + 1} | ||||
| 	configBytes, err := yaml.Marshal(suite.expectedConfig) | ||||
| 	c.Assert(err, IsNil) | ||||
| 	_, err = Parse(configBytes) | ||||
| 	c.Assert(err, NotNil) | ||||
| } | ||||
| 
 | ||||
| func copyConfig(config Configuration) *Configuration { | ||||
| 	configCopy := new(Configuration) | ||||
| 
 | ||||
| 	configCopy.Version = *new(Version) | ||||
| 	configCopy.Version.Major = config.Version.Major | ||||
| 	configCopy.Version.Minor = config.Version.Minor | ||||
| 
 | ||||
| 	configCopy.Registry = *new(Registry) | ||||
| 	configCopy.Registry.LogLevel = config.Registry.LogLevel | ||||
| 
 | ||||
| 	configCopy.Registry.Storage = *new(Storage) | ||||
| 	configCopy.Registry.Storage.Type = config.Registry.Storage.Type | ||||
| 	configCopy.Registry.Storage.Parameters = make(map[string]string) | ||||
| 	for k, v := range config.Registry.Storage.Parameters { | ||||
| 		configCopy.Registry.Storage.Parameters[k] = v | ||||
| 	} | ||||
| 
 | ||||
| 	return configCopy | ||||
| } | ||||
		Loading…
	
		Reference in New Issue