forked from distribution/distribution
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
203 lines (180 loc) · 5.65 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package configuration
import (
"fmt"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"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)
}
// 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 map[string]string
}
// 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), env: make(map[string]string)}
for _, parseInfo := range parseInfos {
p.mapping[parseInfo.Version] = parseInfo
}
for _, env := range os.Environ() {
envParts := strings.SplitN(env, "=", 2)
p.env[envParts[0]] = envParts[1]
}
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
}
err = p.overwriteFields(parseAs, p.prefix)
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
}
func (p *Parser) overwriteFields(v reflect.Value, prefix string) error {
for v.Kind() == reflect.Ptr {
v = reflect.Indirect(v)
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
sf := v.Type().Field(i)
fieldPrefix := strings.ToUpper(prefix + "_" + sf.Name)
if e, ok := p.env[fieldPrefix]; ok {
fieldVal := reflect.New(sf.Type)
err := yaml.Unmarshal([]byte(e), fieldVal.Interface())
if err != nil {
return err
}
v.Field(i).Set(reflect.Indirect(fieldVal))
}
err := p.overwriteFields(v.Field(i), fieldPrefix)
if err != nil {
return err
}
}
case reflect.Map:
p.overwriteMap(v, prefix)
}
return nil
}
func (p *Parser) overwriteMap(m reflect.Value, prefix string) error {
switch m.Type().Elem().Kind() {
case reflect.Struct:
for _, k := range m.MapKeys() {
err := p.overwriteFields(m.MapIndex(k), strings.ToUpper(fmt.Sprintf("%s_%s", prefix, k)))
if err != nil {
return err
}
}
envMapRegexp, err := regexp.Compile(fmt.Sprintf("^%s_([A-Z0-9]+)$", strings.ToUpper(prefix)))
if err != nil {
return err
}
for key, val := range p.env {
if submatches := envMapRegexp.FindStringSubmatch(key); submatches != nil {
mapValue := reflect.New(m.Type().Elem())
err := yaml.Unmarshal([]byte(val), mapValue.Interface())
if err != nil {
return err
}
m.SetMapIndex(reflect.ValueOf(strings.ToLower(submatches[1])), reflect.Indirect(mapValue))
}
}
case reflect.Map:
for _, k := range m.MapKeys() {
err := p.overwriteMap(m.MapIndex(k), strings.ToUpper(fmt.Sprintf("%s_%s", prefix, k)))
if err != nil {
return err
}
}
default:
envMapRegexp, err := regexp.Compile(fmt.Sprintf("^%s_([A-Z0-9]+)$", strings.ToUpper(prefix)))
if err != nil {
return err
}
for key, val := range p.env {
if submatches := envMapRegexp.FindStringSubmatch(key); submatches != nil {
mapValue := reflect.New(m.Type().Elem())
err := yaml.Unmarshal([]byte(val), mapValue.Interface())
if err != nil {
return err
}
m.SetMapIndex(reflect.ValueOf(strings.ToLower(submatches[1])), reflect.Indirect(mapValue))
}
}
}
return nil
}