github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/config/config.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package config provides utilities for loading configuration files. 22 package config 23 24 import ( 25 "errors" 26 "io" 27 "os" 28 "reflect" 29 "strings" 30 31 "go.uber.org/config" 32 "go.uber.org/zap" 33 validator "gopkg.in/validator.v2" 34 "gopkg.in/yaml.v2" 35 ) 36 37 const ( 38 deprecatedPrefix = "Deprecated" 39 ) 40 41 var ( 42 errNoFilesToLoad = errors.New("attempt to load config with no files") 43 44 // osLookupEnv allows simple mocking of os.LookupEnv for the test of that 45 // code path. Use Expand option 46 // for most test cases instead. 47 osLookupEnv = os.LookupEnv 48 ) 49 50 // Options is an options set used when parsing config. 51 type Options struct { 52 DisableUnmarshalStrict bool 53 DisableValidate bool 54 55 // Expand provides values for templated strings of the form ${KEY}. 56 // By default, we extract these values from the environment. 57 Expand config.LookupFunc 58 } 59 60 // LoadFile loads a config from a file. 61 func LoadFile(dst interface{}, file string, opts Options) error { 62 return LoadFiles(dst, []string{file}, opts) 63 } 64 65 // LoadFiles loads a config from list of files. If value for a property is 66 // present in multiple files, the value from the last file will be applied. 67 // Validation is done after merging all values. 68 func LoadFiles(dst interface{}, files []string, opts Options) error { 69 if len(files) == 0 { 70 return errNoFilesToLoad 71 } 72 73 yamlOpts := make([]config.YAMLOption, 0, len(files)) 74 for _, name := range files { 75 yamlOpts = append(yamlOpts, config.File(name)) 76 } 77 78 if opts.DisableUnmarshalStrict { 79 yamlOpts = append(yamlOpts, config.Permissive()) 80 } 81 82 expand := opts.Expand 83 if expand == nil { 84 expand = osLookupEnv 85 } 86 87 yamlOpts = append(yamlOpts, config.Expand(expand)) 88 89 provider, err := config.NewYAML(yamlOpts...) 90 if err != nil { 91 return err 92 } 93 94 if err := provider.Get(config.Root).Populate(dst); err != nil { 95 return err 96 } 97 98 if opts.DisableValidate { 99 return nil 100 } 101 102 return validator.Validate(dst) 103 } 104 105 // Dump writes the given configuration to stream dst as YAML. 106 func Dump(cfg interface{}, dst io.Writer) error { 107 return yaml.NewEncoder(dst).Encode(cfg) 108 } 109 110 // deprecationCheck checks the config for deprecated fields and returns any in 111 // slice of strings. 112 func deprecationCheck(cfg interface{}, df []string) []string { 113 n := reflect.TypeOf(cfg).NumField() 114 for i := 0; i < n; i++ { 115 v := reflect.ValueOf(cfg).Field(i) 116 if v.Kind() == reflect.Struct { 117 df = deprecationCheck(v.Interface(), df) 118 } 119 name := reflect.TypeOf(cfg).Field(i).Name 120 if strings.HasPrefix(name, deprecatedPrefix) && !v.IsZero() { 121 // Exclude unset deprecated config values from 122 // raising warnings via checking IsZero. 123 df = append(df, name) 124 } 125 } 126 return df 127 } 128 129 // WarnOnDeprecation emits a warning for every deprecated field 130 func WarnOnDeprecation(cfg interface{}, logger *zap.Logger) { 131 deprecatedFields := []string{} 132 for _, v := range deprecationCheck(cfg, deprecatedFields) { 133 logger.Warn("using deprecated configuration field", zap.String("field", v)) 134 } 135 }