123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- package configuration
- import (
- "fmt"
- "os"
- "reflect"
- "sort"
- "strconv"
- "strings"
- "github.com/Sirupsen/logrus"
- "gopkg.in/yaml.v2"
- )
- // Version is a major/minor version pair of the form Major.Minor
- // Major version upgrades indicate structure or type changes
- // Minor version upgrades should be strictly additive
- type Version string
- // MajorMinorVersion constructs a Version from its Major and Minor components
- func MajorMinorVersion(major, minor uint) Version {
- return Version(fmt.Sprintf("%d.%d", major, minor))
- }
- func (version Version) major() (uint, error) {
- majorPart := strings.Split(string(version), ".")[0]
- major, err := strconv.ParseUint(majorPart, 10, 0)
- return uint(major), err
- }
- // Major returns the major version portion of a Version
- func (version Version) Major() uint {
- major, _ := version.major()
- return major
- }
- func (version Version) minor() (uint, error) {
- minorPart := strings.Split(string(version), ".")[1]
- minor, err := strconv.ParseUint(minorPart, 10, 0)
- return uint(minor), err
- }
- // Minor returns the minor version portion of a Version
- func (version Version) Minor() uint {
- minor, _ := version.minor()
- return minor
- }
- // VersionedParseInfo defines how a specific version of a configuration should
- // be parsed into the current version
- type VersionedParseInfo struct {
- // Version is the version which this parsing information relates to
- Version Version
- // ParseAs defines the type which a configuration file of this version
- // should be parsed into
- ParseAs reflect.Type
- // ConversionFunc defines a method for converting the parsed configuration
- // (of type ParseAs) into the current configuration version
- // Note: this method signature is very unclear with the absence of generics
- ConversionFunc func(interface{}) (interface{}, error)
- }
- type envVar struct {
- name string
- value string
- }
- type envVars []envVar
- func (a envVars) Len() int { return len(a) }
- func (a envVars) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
- func (a envVars) Less(i, j int) bool { return a[i].name < a[j].name }
- // Parser can be used to parse a configuration file and environment of a defined
- // version into a unified output structure
- type Parser struct {
- prefix string
- mapping map[Version]VersionedParseInfo
- env envVars
- }
- // NewParser returns a *Parser with the given environment prefix which handles
- // versioned configurations which match the given parseInfos
- func NewParser(prefix string, parseInfos []VersionedParseInfo) *Parser {
- p := Parser{prefix: prefix, mapping: make(map[Version]VersionedParseInfo)}
- for _, parseInfo := range parseInfos {
- p.mapping[parseInfo.Version] = parseInfo
- }
- for _, env := range os.Environ() {
- envParts := strings.SplitN(env, "=", 2)
- p.env = append(p.env, envVar{envParts[0], envParts[1]})
- }
- // We must sort the environment variables lexically by name so that
- // more specific variables are applied before less specific ones
- // (i.e. REGISTRY_STORAGE before
- // REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY). This sucks, but it's a
- // lot simpler and easier to get right than unmarshalling map entries
- // into temporaries and merging with the existing entry.
- sort.Sort(p.env)
- return &p
- }
- // Parse reads in the given []byte and environment and writes the resulting
- // configuration into the input v
- //
- // Environment variables may be used to override configuration parameters other
- // than version, following the scheme below:
- // v.Abc may be replaced by the value of PREFIX_ABC,
- // v.Abc.Xyz may be replaced by the value of PREFIX_ABC_XYZ, and so forth
- func (p *Parser) Parse(in []byte, v interface{}) error {
- var versionedStruct struct {
- Version Version
- }
- if err := yaml.Unmarshal(in, &versionedStruct); err != nil {
- return err
- }
- parseInfo, ok := p.mapping[versionedStruct.Version]
- if !ok {
- return fmt.Errorf("Unsupported version: %q", versionedStruct.Version)
- }
- parseAs := reflect.New(parseInfo.ParseAs)
- err := yaml.Unmarshal(in, parseAs.Interface())
- if err != nil {
- return err
- }
- for _, envVar := range p.env {
- pathStr := envVar.name
- if strings.HasPrefix(pathStr, strings.ToUpper(p.prefix)+"_") {
- path := strings.Split(pathStr, "_")
- err = p.overwriteFields(parseAs, pathStr, path[1:], envVar.value)
- if err != nil {
- return err
- }
- }
- }
- c, err := parseInfo.ConversionFunc(parseAs.Interface())
- if err != nil {
- return err
- }
- reflect.ValueOf(v).Elem().Set(reflect.Indirect(reflect.ValueOf(c)))
- return nil
- }
- // overwriteFields replaces configuration values with alternate values specified
- // through the environment. Precondition: an empty path slice must never be
- // passed in.
- func (p *Parser) overwriteFields(v reflect.Value, fullpath string, path []string, payload string) error {
- for v.Kind() == reflect.Ptr {
- if v.IsNil() {
- panic("encountered nil pointer while handling environment variable " + fullpath)
- }
- v = reflect.Indirect(v)
- }
- switch v.Kind() {
- case reflect.Struct:
- return p.overwriteStruct(v, fullpath, path, payload)
- case reflect.Map:
- return p.overwriteMap(v, fullpath, path, payload)
- case reflect.Interface:
- if v.NumMethod() == 0 {
- if !v.IsNil() {
- return p.overwriteFields(v.Elem(), fullpath, path, payload)
- }
- // Interface was empty; create an implicit map
- var template map[string]interface{}
- wrappedV := reflect.MakeMap(reflect.TypeOf(template))
- v.Set(wrappedV)
- return p.overwriteMap(wrappedV, fullpath, path, payload)
- }
- }
- return nil
- }
- func (p *Parser) overwriteStruct(v reflect.Value, fullpath string, path []string, payload string) error {
- // Generate case-insensitive map of struct fields
- byUpperCase := make(map[string]int)
- for i := 0; i < v.NumField(); i++ {
- sf := v.Type().Field(i)
- upper := strings.ToUpper(sf.Name)
- if _, present := byUpperCase[upper]; present {
- panic(fmt.Sprintf("field name collision in configuration object: %s", sf.Name))
- }
- byUpperCase[upper] = i
- }
- fieldIndex, present := byUpperCase[path[0]]
- if !present {
- logrus.Warnf("Ignoring unrecognized environment variable %s", fullpath)
- return nil
- }
- field := v.Field(fieldIndex)
- sf := v.Type().Field(fieldIndex)
- if len(path) == 1 {
- // Env var specifies this field directly
- fieldVal := reflect.New(sf.Type)
- err := yaml.Unmarshal([]byte(payload), fieldVal.Interface())
- if err != nil {
- return err
- }
- field.Set(reflect.Indirect(fieldVal))
- return nil
- }
- // If the field is nil, must create an object
- switch sf.Type.Kind() {
- case reflect.Map:
- if field.IsNil() {
- field.Set(reflect.MakeMap(sf.Type))
- }
- case reflect.Ptr:
- if field.IsNil() {
- field.Set(reflect.New(sf.Type))
- }
- }
- err := p.overwriteFields(field, fullpath, path[1:], payload)
- if err != nil {
- return err
- }
- return nil
- }
- func (p *Parser) overwriteMap(m reflect.Value, fullpath string, path []string, payload string) error {
- if m.Type().Key().Kind() != reflect.String {
- // non-string keys unsupported
- logrus.Warnf("Ignoring environment variable %s involving map with non-string keys", fullpath)
- return nil
- }
- if len(path) > 1 {
- // If a matching key exists, get its value and continue the
- // overwriting process.
- for _, k := range m.MapKeys() {
- if strings.ToUpper(k.String()) == path[0] {
- mapValue := m.MapIndex(k)
- // If the existing value is nil, we want to
- // recreate it instead of using this value.
- if (mapValue.Kind() == reflect.Ptr ||
- mapValue.Kind() == reflect.Interface ||
- mapValue.Kind() == reflect.Map) &&
- mapValue.IsNil() {
- break
- }
- return p.overwriteFields(mapValue, fullpath, path[1:], payload)
- }
- }
- }
- // (Re)create this key
- var mapValue reflect.Value
- if m.Type().Elem().Kind() == reflect.Map {
- mapValue = reflect.MakeMap(m.Type().Elem())
- } else {
- mapValue = reflect.New(m.Type().Elem())
- }
- if len(path) > 1 {
- err := p.overwriteFields(mapValue, fullpath, path[1:], payload)
- if err != nil {
- return err
- }
- } else {
- err := yaml.Unmarshal([]byte(payload), mapValue.Interface())
- if err != nil {
- return err
- }
- }
- m.SetMapIndex(reflect.ValueOf(strings.ToLower(path[0])), reflect.Indirect(mapValue))
- return nil
- }
|