github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/config/fields.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or ≈git-agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "bytes" 21 "fmt" 22 "os" 23 "strconv" 24 "strings" 25 26 "github.com/golang/glog" 27 "github.com/kballard/go-shellquote" 28 flag "github.com/spf13/pflag" 29 apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 30 ) 31 32 type configField interface { 33 typeName() string 34 fieldName() string 35 flagName() string 36 envName() string 37 defaultStr() string 38 applyDefault() 39 clear() 40 present() bool 41 addFlag(f *flag.FlagSet) 42 override(from configField) 43 setFromEnvValue(value string) 44 envValue() string 45 schemaProps() (string, apiext.JSONSchemaProps) 46 description() string 47 } 48 49 type fieldBase struct { 50 field string 51 flag string 52 shorthand string 53 desc string 54 env string 55 } 56 57 func (f *fieldBase) fieldName() string { return f.field } 58 func (f *fieldBase) flagName() string { return f.flag } 59 func (f *fieldBase) envName() string { return f.env } 60 func (f *fieldBase) description() string { return f.desc } 61 62 type stringField struct { 63 fieldBase 64 defValue string 65 pattern string 66 value **string 67 } 68 69 var _ configField = &stringField{} 70 71 func (sf *stringField) typeName() string { return "string" } 72 func (sf *stringField) defaultStr() string { return sf.defValue } 73 func (sf *stringField) applyDefault() { 74 if *sf.value == nil { 75 *sf.value = &sf.defValue 76 } 77 } 78 79 func (sf *stringField) clear() { *sf.value = nil } 80 func (sf *stringField) present() bool { return *sf.value != nil } 81 82 func (sf *stringField) addFlag(f *flag.FlagSet) { 83 f.StringVarP(*sf.value, sf.flag, sf.shorthand, sf.defValue, sf.desc) 84 } 85 86 func (sf *stringField) override(from configField) { 87 fromValue := *from.(*stringField).value 88 if fromValue != nil { 89 v := *fromValue 90 *sf.value = &v 91 } 92 } 93 94 func (sf *stringField) setFromEnvValue(value string) { 95 *sf.value = &value 96 } 97 98 func (sf *stringField) envValue() string { 99 if *sf.value == nil { 100 return "" 101 } 102 return **sf.value 103 } 104 105 func (sf *stringField) schemaProps() (string, apiext.JSONSchemaProps) { 106 return sf.field, apiext.JSONSchemaProps{ 107 Type: "string", 108 Pattern: sf.pattern, 109 } 110 } 111 112 type boolField struct { 113 fieldBase 114 defValue bool 115 value **bool 116 } 117 118 var _ configField = &boolField{} 119 120 func (bf *boolField) typeName() string { return "boolean" } 121 func (bf *boolField) defaultStr() string { return strconv.FormatBool(bf.defValue) } 122 func (bf *boolField) applyDefault() { 123 if *bf.value == nil { 124 *bf.value = &bf.defValue 125 } 126 } 127 128 func (bf *boolField) clear() { *bf.value = nil } 129 func (bf *boolField) present() bool { return *bf.value != nil } 130 131 func (bf *boolField) addFlag(f *flag.FlagSet) { 132 f.BoolVarP(*bf.value, bf.flag, bf.shorthand, bf.defValue, bf.desc) 133 } 134 135 func (bf *boolField) override(from configField) { 136 fromValue := *from.(*boolField).value 137 if fromValue != nil { 138 v := *fromValue 139 *bf.value = &v 140 } 141 } 142 143 func (bf *boolField) setFromEnvValue(value string) { 144 v := value != "" 145 *bf.value = &v 146 } 147 148 func (bf *boolField) envValue() string { 149 if *bf.value == nil || !**bf.value { 150 return "" 151 } 152 return "1" 153 } 154 155 func (bf *boolField) schemaProps() (string, apiext.JSONSchemaProps) { 156 return bf.field, apiext.JSONSchemaProps{ 157 Type: "boolean", 158 } 159 } 160 161 type intField struct { 162 fieldBase 163 defValue int 164 min int 165 max int 166 value **int 167 } 168 169 var _ configField = &intField{} 170 171 func (intf *intField) typeName() string { return "integer" } 172 func (intf *intField) defaultStr() string { return strconv.Itoa(intf.defValue) } 173 func (intf *intField) applyDefault() { 174 if *intf.value == nil { 175 *intf.value = &intf.defValue 176 } 177 } 178 179 func (intf *intField) clear() { *intf.value = nil } 180 func (intf *intField) present() bool { return *intf.value != nil } 181 182 func (intf *intField) addFlag(f *flag.FlagSet) { 183 f.IntVarP(*intf.value, intf.flag, intf.shorthand, intf.defValue, intf.desc) 184 } 185 186 func (intf *intField) override(from configField) { 187 fromValue := *from.(*intField).value 188 if fromValue != nil { 189 v := *fromValue 190 *intf.value = &v 191 } 192 } 193 194 func (intf *intField) setFromEnvValue(value string) { 195 if v, err := strconv.Atoi(value); err != nil { 196 glog.Warningf("bad value for int field %s: %q", intf.field, value) 197 } else { 198 *intf.value = &v 199 } 200 } 201 202 func (intf *intField) envValue() string { 203 if *intf.value == nil { 204 return "" 205 } 206 return strconv.Itoa(**intf.value) 207 } 208 209 func (intf *intField) schemaProps() (string, apiext.JSONSchemaProps) { 210 min := float64(intf.min) 211 max := float64(intf.max) 212 return intf.field, apiext.JSONSchemaProps{ 213 Type: "integer", 214 Minimum: &min, 215 Maximum: &max, 216 } 217 } 218 219 type envLookup func(name string) (string, bool) 220 221 type fieldSet struct { 222 fields []configField 223 docTitle string 224 desc string 225 } 226 227 func (fs *fieldSet) addStringFieldWithPattern(field, flag, shorthand, desc, env, defValue, pattern string, value **string) { 228 fs.addField(&stringField{ 229 fieldBase{field, flag, shorthand, desc, env}, 230 defValue, 231 pattern, 232 value, 233 }) 234 } 235 236 func (fs *fieldSet) addStringField(field, flag, shorthand, desc, env, defValue string, value **string) { 237 fs.addStringFieldWithPattern(field, flag, shorthand, desc, env, defValue, "", value) 238 } 239 240 func (fs *fieldSet) addBoolField(field, flag, shorthand, desc, env string, defValue bool, value **bool) { 241 fs.addField(&boolField{ 242 fieldBase{field, flag, shorthand, desc, env}, 243 defValue, 244 value, 245 }) 246 } 247 248 func (fs *fieldSet) addIntField(field, flag, shorthand, desc, env string, defValue, min, max int, value **int) { 249 fs.addField(&intField{ 250 fieldBase{field, flag, shorthand, desc, env}, 251 defValue, 252 min, 253 max, 254 value, 255 }) 256 } 257 258 func (fs *fieldSet) addField(cf configField) { 259 fs.fields = append(fs.fields, cf) 260 } 261 262 func (fs *fieldSet) applyDefaults() { 263 for _, f := range fs.fields { 264 f.applyDefault() 265 } 266 } 267 268 func (fs *fieldSet) addFlags(flagSet *flag.FlagSet) { 269 for _, f := range fs.fields { 270 if f.flagName() != "" && !strings.Contains(f.flagName(), "+") { 271 f.addFlag(flagSet) 272 } 273 } 274 } 275 276 func (fs *fieldSet) override(from *fieldSet) { 277 for n, f := range fs.fields { 278 f.override(from.fields[n]) 279 } 280 } 281 282 func (fs *fieldSet) copyFrom(from *fieldSet) { 283 for n, f := range fs.fields { 284 f.clear() 285 f.override(from.fields[n]) 286 } 287 } 288 289 func (fs *fieldSet) clearFieldsNotInFlagSet(flagSet *flag.FlagSet) { 290 for _, f := range fs.fields { 291 if flagSet == nil || f.flagName() == "" || !flagSet.Changed(f.flagName()) { 292 f.clear() 293 } 294 } 295 } 296 297 func (fs *fieldSet) setFromEnv(lookupEnv envLookup) { 298 if lookupEnv == nil { 299 lookupEnv = os.LookupEnv 300 } 301 for _, f := range fs.fields { 302 if f.envName() == "" { 303 continue 304 } 305 if v, found := lookupEnv(f.envName()); found { 306 f.setFromEnvValue(v) 307 } 308 } 309 } 310 311 func (fs *fieldSet) dumpEnv() string { 312 var buf bytes.Buffer 313 for _, f := range fs.fields { 314 if f.envName() != "" && f.present() { 315 fmt.Fprintf(&buf, "export %s=%s\n", f.envName(), shellquote.Join(f.envValue())) 316 } 317 } 318 return buf.String() 319 } 320 321 func (fs *fieldSet) schemaProps() map[string]apiext.JSONSchemaProps { 322 r := make(map[string]apiext.JSONSchemaProps) 323 for _, f := range fs.fields { 324 field, props := f.schemaProps() 325 r[field] = props 326 } 327 return r 328 } 329 330 func (fs *fieldSet) generateDoc() string { 331 var buf bytes.Buffer 332 fmt.Fprintf(&buf, 333 "| Description | Config field | Default value | Type | Command line flag / Env |\n"+ 334 "| --- | --- | --- | --- | --- |\n") 335 esc := func(s string) string { return strings.Replace(s, "|", "\\|", -1) } 336 code := func(s string) string { 337 if s == "" { 338 return "" 339 } 340 return fmt.Sprintf("`%s`", esc(s)) 341 } 342 for _, f := range fs.fields { 343 if f.description() == "" { 344 continue 345 } 346 var flagEnv []string 347 if f.flagName() != "" { 348 flagEnv = append(flagEnv, code("--"+strings.Replace(f.flagName(), "+", "", -1))) 349 } 350 if f.envName() != "" { 351 flagEnv = append(flagEnv, code(f.envName())) 352 } 353 fmt.Fprintf(&buf, "| %s | %s | %s | %s | %s |\n", 354 esc(f.description()), 355 code(f.fieldName()), 356 code(f.defaultStr()), 357 f.typeName(), 358 strings.Join(flagEnv, " / ")) 359 } 360 return buf.String() 361 }