github.com/hyperledger-labs/bdls@v2.1.1+incompatible/common/viperutil/config_util.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package viperutil 8 9 import ( 10 "encoding/json" 11 "encoding/pem" 12 "fmt" 13 "io/ioutil" 14 "math" 15 "reflect" 16 "regexp" 17 "strconv" 18 "strings" 19 "time" 20 21 "github.com/Shopify/sarama" 22 version "github.com/hashicorp/go-version" 23 "github.com/hyperledger/fabric/bccsp/factory" 24 "github.com/hyperledger/fabric/common/flogging" 25 "github.com/mitchellh/mapstructure" 26 "github.com/pkg/errors" 27 "github.com/spf13/viper" 28 ) 29 30 var logger = flogging.MustGetLogger("viperutil") 31 32 type viperGetter func(key string) interface{} 33 34 func getKeysRecursively(base string, getKey viperGetter, nodeKeys map[string]interface{}, oType reflect.Type) map[string]interface{} { 35 subTypes := map[string]reflect.Type{} 36 37 if oType != nil && oType.Kind() == reflect.Struct { 38 outer: 39 for i := 0; i < oType.NumField(); i++ { 40 fieldName := oType.Field(i).Name 41 fieldType := oType.Field(i).Type 42 43 for key := range nodeKeys { 44 if strings.EqualFold(fieldName, key) { 45 subTypes[key] = fieldType 46 continue outer 47 } 48 } 49 50 subTypes[fieldName] = fieldType 51 nodeKeys[fieldName] = nil 52 } 53 } 54 55 result := make(map[string]interface{}) 56 for key := range nodeKeys { 57 fqKey := base + key 58 59 val := getKey(fqKey) 60 if m, ok := val.(map[interface{}]interface{}); ok { 61 logger.Debugf("Found map[interface{}]interface{} value for %s", fqKey) 62 tmp := make(map[string]interface{}) 63 for ik, iv := range m { 64 cik, ok := ik.(string) 65 if !ok { 66 panic("Non string key-entry") 67 } 68 tmp[cik] = iv 69 } 70 result[key] = getKeysRecursively(fqKey+".", getKey, tmp, subTypes[key]) 71 } else if m, ok := val.(map[string]interface{}); ok { 72 logger.Debugf("Found map[string]interface{} value for %s", fqKey) 73 result[key] = getKeysRecursively(fqKey+".", getKey, m, subTypes[key]) 74 } else if m, ok := unmarshalJSON(val); ok { 75 logger.Debugf("Found real value for %s setting to map[string]string %v", fqKey, m) 76 result[key] = m 77 } else { 78 if val == nil { 79 fileSubKey := fqKey + ".File" 80 fileVal := getKey(fileSubKey) 81 if fileVal != nil { 82 result[key] = map[string]interface{}{"File": fileVal} 83 continue 84 } 85 } 86 logger.Debugf("Found real value for %s setting to %T %v", fqKey, val, val) 87 result[key] = val 88 89 } 90 } 91 return result 92 } 93 94 func unmarshalJSON(val interface{}) (map[string]string, bool) { 95 mp := map[string]string{} 96 s, ok := val.(string) 97 if !ok { 98 logger.Debugf("Unmarshal JSON: value is not a string: %v", val) 99 return nil, false 100 } 101 err := json.Unmarshal([]byte(s), &mp) 102 if err != nil { 103 logger.Debugf("Unmarshal JSON: value cannot be unmarshalled: %s", err) 104 return nil, false 105 } 106 return mp, true 107 } 108 109 // customDecodeHook adds the additional functions of parsing durations from strings 110 // as well as parsing strings of the format "[thing1, thing2, thing3]" into string slices 111 // Note that whitespace around slice elements is removed 112 func customDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 113 durationHook := mapstructure.StringToTimeDurationHookFunc() 114 dur, err := mapstructure.DecodeHookExec(durationHook, f, t, data) 115 if err == nil { 116 if _, ok := dur.(time.Duration); ok { 117 return dur, nil 118 } 119 } 120 121 if f.Kind() != reflect.String { 122 return data, nil 123 } 124 125 raw := data.(string) 126 l := len(raw) 127 if l > 1 && raw[0] == '[' && raw[l-1] == ']' { 128 slice := strings.Split(raw[1:l-1], ",") 129 for i, v := range slice { 130 slice[i] = strings.TrimSpace(v) 131 } 132 return slice, nil 133 } 134 135 return data, nil 136 } 137 138 func byteSizeDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { 139 if f != reflect.String || t != reflect.Uint32 { 140 return data, nil 141 } 142 raw := data.(string) 143 if raw == "" { 144 return data, nil 145 } 146 var re = regexp.MustCompile(`^(?P<size>[0-9]+)\s*(?i)(?P<unit>(k|m|g))b?$`) 147 if re.MatchString(raw) { 148 size, err := strconv.ParseUint(re.ReplaceAllString(raw, "${size}"), 0, 64) 149 if err != nil { 150 return data, nil 151 } 152 unit := re.ReplaceAllString(raw, "${unit}") 153 switch strings.ToLower(unit) { 154 case "g": 155 size = size << 10 156 fallthrough 157 case "m": 158 size = size << 10 159 fallthrough 160 case "k": 161 size = size << 10 162 } 163 if size > math.MaxUint32 { 164 return size, fmt.Errorf("value '%s' overflows uint32", raw) 165 } 166 return size, nil 167 } 168 return data, nil 169 } 170 171 func stringFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { 172 // "to" type should be string 173 if t != reflect.String { 174 return data, nil 175 } 176 // "from" type should be map 177 if f != reflect.Map { 178 return data, nil 179 } 180 v := reflect.ValueOf(data) 181 switch v.Kind() { 182 case reflect.String: 183 return data, nil 184 case reflect.Map: 185 d := data.(map[string]interface{}) 186 fileName, ok := d["File"] 187 if !ok { 188 fileName, ok = d["file"] 189 } 190 switch { 191 case ok && fileName != nil: 192 bytes, err := ioutil.ReadFile(fileName.(string)) 193 if err != nil { 194 return data, err 195 } 196 return string(bytes), nil 197 case ok: 198 // fileName was nil 199 return nil, fmt.Errorf("Value of File: was nil") 200 } 201 } 202 return data, nil 203 } 204 205 func pemBlocksFromFileDecodeHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface{}, error) { 206 // "to" type should be string 207 if t != reflect.Slice { 208 return data, nil 209 } 210 // "from" type should be map 211 if f != reflect.Map { 212 return data, nil 213 } 214 v := reflect.ValueOf(data) 215 switch v.Kind() { 216 case reflect.String: 217 return data, nil 218 case reflect.Map: 219 var fileName string 220 var ok bool 221 switch d := data.(type) { 222 case map[string]string: 223 fileName, ok = d["File"] 224 if !ok { 225 fileName, ok = d["file"] 226 } 227 case map[string]interface{}: 228 var fileI interface{} 229 fileI, ok = d["File"] 230 if !ok { 231 fileI = d["file"] 232 } 233 fileName, ok = fileI.(string) 234 } 235 236 switch { 237 case ok && fileName != "": 238 var result []string 239 bytes, err := ioutil.ReadFile(fileName) 240 if err != nil { 241 return data, err 242 } 243 for len(bytes) > 0 { 244 var block *pem.Block 245 block, bytes = pem.Decode(bytes) 246 if block == nil { 247 break 248 } 249 if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { 250 continue 251 } 252 result = append(result, string(pem.EncodeToMemory(block))) 253 } 254 return result, nil 255 case ok: 256 // fileName was nil 257 return nil, fmt.Errorf("Value of File: was nil") 258 } 259 } 260 return data, nil 261 } 262 263 var kafkaVersionConstraints map[sarama.KafkaVersion]version.Constraints 264 265 func init() { 266 kafkaVersionConstraints = make(map[sarama.KafkaVersion]version.Constraints) 267 kafkaVersionConstraints[sarama.V0_8_2_0], _ = version.NewConstraint(">=0.8.2,<0.8.2.1") 268 kafkaVersionConstraints[sarama.V0_8_2_1], _ = version.NewConstraint(">=0.8.2.1,<0.8.2.2") 269 kafkaVersionConstraints[sarama.V0_8_2_2], _ = version.NewConstraint(">=0.8.2.2,<0.9.0.0") 270 kafkaVersionConstraints[sarama.V0_9_0_0], _ = version.NewConstraint(">=0.9.0.0,<0.9.0.1") 271 kafkaVersionConstraints[sarama.V0_9_0_1], _ = version.NewConstraint(">=0.9.0.1,<0.10.0.0") 272 kafkaVersionConstraints[sarama.V0_10_0_0], _ = version.NewConstraint(">=0.10.0.0,<0.10.0.1") 273 kafkaVersionConstraints[sarama.V0_10_0_1], _ = version.NewConstraint(">=0.10.0.1,<0.10.1.0") 274 kafkaVersionConstraints[sarama.V0_10_1_0], _ = version.NewConstraint(">=0.10.1.0,<0.10.2.0") 275 kafkaVersionConstraints[sarama.V0_10_2_0], _ = version.NewConstraint(">=0.10.2.0,<0.11.0.0") 276 kafkaVersionConstraints[sarama.V0_11_0_0], _ = version.NewConstraint(">=0.11.0.0,<1.0.0") 277 kafkaVersionConstraints[sarama.V1_0_0_0], _ = version.NewConstraint(">=1.0.0") 278 } 279 280 func kafkaVersionDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 281 if f.Kind() != reflect.String || t != reflect.TypeOf(sarama.KafkaVersion{}) { 282 return data, nil 283 } 284 285 v, err := version.NewVersion(data.(string)) 286 if err != nil { 287 return nil, fmt.Errorf("Unable to parse Kafka version: %s", err) 288 } 289 290 for kafkaVersion, constraints := range kafkaVersionConstraints { 291 if constraints.Check(v) { 292 return kafkaVersion, nil 293 } 294 } 295 296 return nil, fmt.Errorf("Unsupported Kafka version: '%s'", data) 297 } 298 299 func bccspHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 300 if t != reflect.TypeOf(&factory.FactoryOpts{}) { 301 return data, nil 302 } 303 304 config := factory.GetDefaultOpts() 305 306 err := mapstructure.Decode(data, config) 307 if err != nil { 308 return nil, errors.Wrap(err, "could not decode bcssp type") 309 } 310 311 return config, nil 312 } 313 314 // EnhancedExactUnmarshal is intended to unmarshal a config file into a structure 315 // producing error when extraneous variables are introduced and supporting 316 // the time.Duration type 317 func EnhancedExactUnmarshal(v *viper.Viper, output interface{}) error { 318 oType := reflect.TypeOf(output) 319 if oType.Kind() != reflect.Ptr { 320 return errors.Errorf("supplied output argument must be a pointer to a struct but is not pointer") 321 } 322 eType := oType.Elem() 323 if eType.Kind() != reflect.Struct { 324 return errors.Errorf("supplied output argument must be a pointer to a struct, but it is pointer to something else") 325 } 326 327 baseKeys := v.AllSettings() 328 329 getterWithClass := func(key string) interface{} { return v.Get(key) } // hide receiver 330 leafKeys := getKeysRecursively("", getterWithClass, baseKeys, eType) 331 332 logger.Debugf("%+v", leafKeys) 333 config := &mapstructure.DecoderConfig{ 334 ErrorUnused: true, 335 Metadata: nil, 336 Result: output, 337 WeaklyTypedInput: true, 338 DecodeHook: mapstructure.ComposeDecodeHookFunc( 339 bccspHook, 340 customDecodeHook, 341 byteSizeDecodeHook, 342 stringFromFileDecodeHook, 343 pemBlocksFromFileDecodeHook, 344 kafkaVersionDecodeHook, 345 ), 346 } 347 348 decoder, err := mapstructure.NewDecoder(config) 349 if err != nil { 350 return err 351 } 352 return decoder.Decode(leafKeys) 353 }