istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/env/var.go (about) 1 // Copyright 2019 Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package env makes it possible to track use of environment variables within a procress 16 // in order to generate documentation for these uses. 17 package env 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "os" 23 "sort" 24 "strconv" 25 "sync" 26 "time" 27 28 "istio.io/istio/pkg/log" 29 ) 30 31 // The type of a variable's value 32 type VarType byte 33 34 const ( 35 // Variable holds a free-form string. 36 STRING VarType = iota 37 // Variable holds a boolean value. 38 BOOL 39 // Variable holds a signed integer. 40 INT 41 // Variables holds a floating point value. 42 FLOAT 43 // Variable holds a time duration. 44 DURATION 45 // Variable holds a dynamic unknown type. 46 OTHER 47 ) 48 49 // Var describes a single environment variable 50 type Var struct { 51 // The name of the environment variable. 52 Name string 53 54 // The optional default value of the environment variable. 55 DefaultValue string 56 57 // Description of the environment variable's purpose. 58 Description string 59 60 // Hide the existence of this variable when outputting usage information. 61 Hidden bool 62 63 // Mark this variable as deprecated when generating usage information. 64 Deprecated bool 65 66 // The type of the variable's value 67 Type VarType 68 69 // The underlying Go type of the variable 70 GoType string 71 } 72 73 // StringVar represents a single string environment variable. 74 type StringVar struct { 75 Var 76 } 77 78 // BoolVar represents a single boolean environment variable. 79 type BoolVar struct { 80 Var 81 } 82 83 // IntVar represents a single integer environment variable. 84 type IntVar struct { 85 Var 86 } 87 88 // FloatVar represents a single floating-point environment variable. 89 type FloatVar struct { 90 Var 91 } 92 93 // DurationVar represents a single duration environment variable. 94 type DurationVar struct { 95 Var 96 } 97 98 var ( 99 allVars = make(map[string]Var) 100 mutex sync.Mutex 101 ) 102 103 // VarDescriptions returns a description of this process' environment variables, sorted by name. 104 func VarDescriptions() []Var { 105 mutex.Lock() 106 sorted := make([]Var, 0, len(allVars)) 107 for _, v := range allVars { 108 sorted = append(sorted, v) 109 } 110 mutex.Unlock() 111 112 sort.Slice(sorted, func(i, j int) bool { 113 return sorted[i].Name < sorted[j].Name 114 }) 115 116 return sorted 117 } 118 119 type Parseable interface { 120 comparable 121 } 122 123 type GenericVar[T Parseable] struct { 124 Var 125 delegate specializedVar[T] 126 } 127 128 func Register[T Parseable](name string, defaultValue T, description string) GenericVar[T] { 129 // Specialized cases 130 // In the future, once only Register() remains, we can likely drop most of these. 131 // however, time.Duration is needed still as it doesn't implement json 132 switch d := any(defaultValue).(type) { 133 case time.Duration: 134 v := RegisterDurationVar(name, d, description) 135 return GenericVar[T]{v.Var, any(v).(specializedVar[T])} 136 case string: 137 v := RegisterStringVar(name, d, description) 138 return GenericVar[T]{v.Var, any(v).(specializedVar[T])} 139 case float64: 140 v := RegisterFloatVar(name, d, description) 141 return GenericVar[T]{v.Var, any(v).(specializedVar[T])} 142 case int: 143 v := RegisterIntVar(name, d, description) 144 return GenericVar[T]{v.Var, any(v).(specializedVar[T])} 145 case bool: 146 v := RegisterBoolVar(name, d, description) 147 return GenericVar[T]{v.Var, any(v).(specializedVar[T])} 148 } 149 b, _ := json.Marshal(defaultValue) 150 v := Var{Name: name, DefaultValue: string(b), Description: description, Type: STRING, GoType: fmt.Sprintf("%T", defaultValue)} 151 RegisterVar(v) 152 return GenericVar[T]{getVar(name), nil} 153 } 154 155 // RegisterStringVar registers a new string environment variable. 156 func RegisterStringVar(name string, defaultValue string, description string) StringVar { 157 v := Var{Name: name, DefaultValue: defaultValue, Description: description, Type: STRING} 158 RegisterVar(v) 159 return StringVar{getVar(name)} 160 } 161 162 // RegisterBoolVar registers a new boolean environment variable. 163 func RegisterBoolVar(name string, defaultValue bool, description string) BoolVar { 164 v := Var{Name: name, DefaultValue: strconv.FormatBool(defaultValue), Description: description, Type: BOOL} 165 RegisterVar(v) 166 return BoolVar{getVar(name)} 167 } 168 169 // RegisterIntVar registers a new integer environment variable. 170 func RegisterIntVar(name string, defaultValue int, description string) IntVar { 171 v := Var{Name: name, DefaultValue: strconv.FormatInt(int64(defaultValue), 10), Description: description, Type: INT} 172 RegisterVar(v) 173 return IntVar{getVar(name)} 174 } 175 176 // RegisterFloatVar registers a new floating-point environment variable. 177 func RegisterFloatVar(name string, defaultValue float64, description string) FloatVar { 178 v := Var{Name: name, DefaultValue: strconv.FormatFloat(defaultValue, 'G', -1, 64), Description: description, Type: FLOAT} 179 RegisterVar(v) 180 return FloatVar{v} 181 } 182 183 // RegisterDurationVar registers a new duration environment variable. 184 func RegisterDurationVar(name string, defaultValue time.Duration, description string) DurationVar { 185 v := Var{Name: name, DefaultValue: defaultValue.String(), Description: description, Type: DURATION} 186 RegisterVar(v) 187 return DurationVar{getVar(name)} 188 } 189 190 // RegisterVar registers a generic environment variable. 191 func RegisterVar(v Var) { 192 mutex.Lock() 193 194 if old, ok := allVars[v.Name]; ok { 195 if v.Description != "" { 196 allVars[v.Name] = v // last one with a description wins if the same variable name is registered multiple times 197 } 198 199 if old.Description != v.Description || old.DefaultValue != v.DefaultValue || old.Type != v.Type || old.Deprecated != v.Deprecated || old.Hidden != v.Hidden { 200 log.Warnf("The environment variable %s was registered multiple times using different metadata: %v, %v", v.Name, old, v) 201 } 202 } else { 203 allVars[v.Name] = v 204 } 205 206 mutex.Unlock() 207 } 208 209 func getVar(name string) Var { 210 mutex.Lock() 211 result := allVars[name] 212 mutex.Unlock() 213 214 return result 215 } 216 217 // Get retrieves the value of the environment variable. 218 // It returns the value, which will be the default if the variable is not present. 219 // To distinguish between an empty value and an unset value, use Lookup. 220 func (v StringVar) Get() string { 221 result, _ := v.Lookup() 222 return result 223 } 224 225 // Lookup retrieves the value of the environment variable. If the 226 // variable is present in the environment the 227 // value (which may be empty) is returned and the boolean is true. 228 // Otherwise the returned value will be the default and the boolean will 229 // be false. 230 func (v StringVar) Lookup() (string, bool) { 231 result, ok := os.LookupEnv(v.Name) 232 if !ok { 233 result = v.DefaultValue 234 } 235 236 return result, ok 237 } 238 239 // Get retrieves the value of the environment variable. 240 // It returns the value, which will be the default if the variable is not present. 241 // To distinguish between an empty value and an unset value, use Lookup. 242 func (v BoolVar) Get() bool { 243 result, _ := v.Lookup() 244 return result 245 } 246 247 // Lookup retrieves the value of the environment variable. If the 248 // variable is present in the environment the 249 // value (which may be empty) is returned and the boolean is true. 250 // Otherwise the returned value will be the default and the boolean will 251 // be false. 252 func (v BoolVar) Lookup() (bool, bool) { 253 result, ok := os.LookupEnv(v.Name) 254 if !ok { 255 result = v.DefaultValue 256 } 257 258 b, err := strconv.ParseBool(result) 259 if err != nil { 260 log.Warnf("Invalid environment variable value `%s`, expecting true/false, defaulting to %v", result, v.DefaultValue) 261 b, _ = strconv.ParseBool(v.DefaultValue) 262 } 263 264 return b, ok 265 } 266 267 // Get retrieves the value of the environment variable. 268 // It returns the value, which will be the default if the variable is not present. 269 // To distinguish between an empty value and an unset value, use Lookup. 270 func (v IntVar) Get() int { 271 result, _ := v.Lookup() 272 return result 273 } 274 275 // Lookup retrieves the value of the environment variable. If the 276 // variable is present in the environment the 277 // value (which may be empty) is returned and the boolean is true. 278 // Otherwise the returned value will be the default and the boolean will 279 // be false. 280 func (v IntVar) Lookup() (int, bool) { 281 result, ok := os.LookupEnv(v.Name) 282 if !ok { 283 result = v.DefaultValue 284 } 285 286 i, err := strconv.Atoi(result) 287 if err != nil { 288 log.Warnf("Invalid environment variable value `%s`, expecting an integer, defaulting to %v", result, v.DefaultValue) 289 i, _ = strconv.Atoi(v.DefaultValue) 290 } 291 292 return i, ok 293 } 294 295 // Get retrieves the value of the environment variable. 296 // It returns the value, which will be the default if the variable is not present. 297 // To distinguish between an empty value and an unset value, use Lookup. 298 func (v FloatVar) Get() float64 { 299 result, _ := v.Lookup() 300 return result 301 } 302 303 // Lookup retrieves the value of the environment variable. If the 304 // variable is present in the environment the 305 // value (which may be empty) is returned and the boolean is true. 306 // Otherwise the returned value will be the default and the boolean will 307 // be false. 308 func (v FloatVar) Lookup() (float64, bool) { 309 result, ok := os.LookupEnv(v.Name) 310 if !ok { 311 result = v.DefaultValue 312 } 313 314 f, err := strconv.ParseFloat(result, 64) 315 if err != nil { 316 log.Warnf("Invalid environment variable value `%s`, expecting a floating-point value, defaulting to %v", result, v.DefaultValue) 317 f, _ = strconv.ParseFloat(v.DefaultValue, 64) 318 } 319 320 return f, ok 321 } 322 323 // Get retrieves the value of the environment variable. 324 // It returns the value, which will be the default if the variable is not present. 325 // To distinguish between an empty value and an unset value, use Lookup. 326 func (v DurationVar) Get() time.Duration { 327 result, _ := v.Lookup() 328 return result 329 } 330 331 // Lookup retrieves the value of the environment variable. If the 332 // variable is present in the environment the 333 // value (which may be empty) is returned and the boolean is true. 334 // Otherwise the returned value will be the default and the boolean will 335 // be false. 336 func (v DurationVar) Lookup() (time.Duration, bool) { 337 result, ok := os.LookupEnv(v.Name) 338 if !ok { 339 result = v.DefaultValue 340 } 341 342 d, err := time.ParseDuration(result) 343 if err != nil { 344 log.Warnf("Invalid environment variable value `%s`, expecting a duration, defaulting to %v", result, v.DefaultValue) 345 d, _ = time.ParseDuration(v.DefaultValue) 346 } 347 348 return d, ok 349 } 350 351 // Get retrieves the value of the environment variable. 352 // It returns the value, which will be the default if the variable is not present. 353 // To distinguish between an empty value and an unset value, use Lookup. 354 func (v GenericVar[T]) Get() T { 355 if v.delegate != nil { 356 return v.delegate.Get() 357 } 358 result, _ := v.Lookup() 359 return result 360 } 361 362 // Lookup retrieves the value of the environment variable. If the 363 // variable is present in the environment the 364 // value (which may be empty) is returned and the boolean is true. 365 // Otherwise the returned value will be the default and the boolean will 366 // be false. 367 func (v GenericVar[T]) Lookup() (T, bool) { 368 if v.delegate != nil { 369 return v.delegate.Lookup() 370 } 371 result, ok := os.LookupEnv(v.Name) 372 if !ok { 373 result = v.DefaultValue 374 } 375 376 res := new(T) 377 378 if err := json.Unmarshal([]byte(result), res); err != nil { 379 log.Warnf("Invalid environment variable value `%s` defaulting to %v: %v", result, v.DefaultValue, err) 380 _ = json.Unmarshal([]byte(v.DefaultValue), res) 381 } 382 383 return *res, ok 384 } 385 386 func (v GenericVar[T]) IsSet() bool { 387 _, ok := v.Lookup() 388 return ok 389 } 390 391 func (v GenericVar[T]) GetName() string { 392 return v.Var.Name 393 } 394 395 // specializedVar represents a var that can Get/Lookup 396 type specializedVar[T any] interface { 397 Lookup() (T, bool) 398 Get() T 399 } 400 401 // VariableInfo provides generic information about a variable. All Variables implement this interface. 402 // This is largely to workaround lack of covariance in Go. 403 type VariableInfo interface { 404 GetName() string 405 IsSet() bool 406 }