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. 17package env 18 19import ( 20 "os" 21 "sort" 22 "strconv" 23 "sync" 24 "time" 25 26 "istio.io/pkg/log" 27) 28 29// The type of a variable's value 30type VarType byte 31 32const ( 33 // Variable holds a free-form string. 34 STRING VarType = iota 35 // Variable holds a boolean value. 36 BOOL 37 // Variable holds a signed integer. 38 INT 39 // Variables holds a floating point value. 40 FLOAT 41 // Variable holds a time duration. 42 DURATION 43) 44 45// Var describes a single environment variable 46type Var struct { 47 // The name of the environment variable. 48 Name string 49 50 // The optional default value of the environment variable. 51 DefaultValue string 52 53 // Description of the environment variable's purpose. 54 Description string 55 56 // Hide the existence of this variable when outputting usage information. 57 Hidden bool 58 59 // Mark this variable as deprecated when generating usage information. 60 Deprecated bool 61 62 // The type of the variable's value 63 Type VarType 64} 65 66// StringVar represents a single string environment variable. 67type StringVar struct { 68 Var 69} 70 71// BoolVar represents a single boolean environment variable. 72type BoolVar struct { 73 Var 74} 75 76// IntVar represents a single integer environment variable. 77type IntVar struct { 78 Var 79} 80 81// FloatVar represents a single floating-point environment variable. 82type FloatVar struct { 83 Var 84} 85 86// DurationVar represents a single duration environment variable. 87type DurationVar struct { 88 Var 89} 90 91var allVars = make(map[string]Var) 92var mutex sync.Mutex 93 94// VarDescriptions returns a description of this process' environment variables, sorted by name. 95func VarDescriptions() []Var { 96 mutex.Lock() 97 sorted := make([]Var, 0, len(allVars)) 98 for _, v := range allVars { 99 sorted = append(sorted, v) 100 } 101 mutex.Unlock() 102 103 sort.Slice(sorted, func(i, j int) bool { 104 return sorted[i].Name < sorted[j].Name 105 }) 106 107 return sorted 108} 109 110// RegisterStringVar registers a new string environment variable. 111func RegisterStringVar(name string, defaultValue string, description string) StringVar { 112 v := Var{Name: name, DefaultValue: defaultValue, Description: description, Type: STRING} 113 RegisterVar(v) 114 return StringVar{getVar(name)} 115} 116 117// RegisterBoolVar registers a new boolean environment variable. 118func RegisterBoolVar(name string, defaultValue bool, description string) BoolVar { 119 v := Var{Name: name, DefaultValue: strconv.FormatBool(defaultValue), Description: description, Type: BOOL} 120 RegisterVar(v) 121 return BoolVar{getVar(name)} 122} 123 124// RegisterIntVar registers a new integer environment variable. 125func RegisterIntVar(name string, defaultValue int, description string) IntVar { 126 v := Var{Name: name, DefaultValue: strconv.FormatInt(int64(defaultValue), 10), Description: description, Type: INT} 127 RegisterVar(v) 128 return IntVar{getVar(name)} 129} 130 131// RegisterFloatVar registers a new floating-point environment variable. 132func RegisterFloatVar(name string, defaultValue float64, description string) FloatVar { 133 v := Var{Name: name, DefaultValue: strconv.FormatFloat(defaultValue, 'G', -1, 64), Description: description, Type: FLOAT} 134 RegisterVar(v) 135 return FloatVar{v} 136} 137 138// RegisterDurationVar registers a new duration environment variable. 139func RegisterDurationVar(name string, defaultValue time.Duration, description string) DurationVar { 140 v := Var{Name: name, DefaultValue: defaultValue.String(), Description: description, Type: DURATION} 141 RegisterVar(v) 142 return DurationVar{getVar(name)} 143} 144 145// RegisterVar registers a generic environment variable. 146func RegisterVar(v Var) { 147 mutex.Lock() 148 149 if old, ok := allVars[v.Name]; ok { 150 if v.Description != "" { 151 allVars[v.Name] = v // last one with a description wins if the same variable name is registered multiple times 152 } 153 154 if old.Description != v.Description || old.DefaultValue != v.DefaultValue || old.Type != v.Type || old.Deprecated != v.Deprecated || old.Hidden != v.Hidden { 155 log.Warnf("The environment variable %s was registered multiple times using different metadata: %v, %v", v.Name, old, v) 156 } 157 } else { 158 allVars[v.Name] = v 159 } 160 161 mutex.Unlock() 162} 163 164func getVar(name string) Var { 165 mutex.Lock() 166 result := allVars[name] 167 mutex.Unlock() 168 169 return result 170} 171 172// Get retrieves the value of the environment variable. 173// It returns the value, which will be the default if the variable is not present. 174// To distinguish between an empty value and an unset value, use Lookup. 175func (v StringVar) Get() string { 176 result, _ := v.Lookup() 177 return result 178} 179 180// Lookup retrieves the value of the environment variable. If the 181// variable is present in the environment the 182// value (which may be empty) is returned and the boolean is true. 183// Otherwise the returned value will be the default and the boolean will 184// be false. 185func (v StringVar) Lookup() (string, bool) { 186 result, ok := os.LookupEnv(v.Name) 187 if !ok { 188 result = v.DefaultValue 189 } 190 191 return result, ok 192} 193 194// Get retrieves the value of the environment variable. 195// It returns the value, which will be the default if the variable is not present. 196// To distinguish between an empty value and an unset value, use Lookup. 197func (v BoolVar) Get() bool { 198 result, _ := v.Lookup() 199 return result 200} 201 202// Lookup retrieves the value of the environment variable. If the 203// variable is present in the environment the 204// value (which may be empty) is returned and the boolean is true. 205// Otherwise the returned value will be the default and the boolean will 206// be false. 207func (v BoolVar) Lookup() (bool, bool) { 208 result, ok := os.LookupEnv(v.Name) 209 if !ok { 210 result = v.DefaultValue 211 } 212 213 b, err := strconv.ParseBool(result) 214 if err != nil { 215 log.Warnf("Invalid environment variable value `%s`, expecting true/false, defaulting to %v", result, v.DefaultValue) 216 b, _ = strconv.ParseBool(v.DefaultValue) 217 } 218 219 return b, ok 220} 221 222// Get retrieves the value of the environment variable. 223// It returns the value, which will be the default if the variable is not present. 224// To distinguish between an empty value and an unset value, use Lookup. 225func (v IntVar) Get() int { 226 result, _ := v.Lookup() 227 return result 228} 229 230// Lookup retrieves the value of the environment variable. If the 231// variable is present in the environment the 232// value (which may be empty) is returned and the boolean is true. 233// Otherwise the returned value will be the default and the boolean will 234// be false. 235func (v IntVar) Lookup() (int, bool) { 236 result, ok := os.LookupEnv(v.Name) 237 if !ok { 238 result = v.DefaultValue 239 } 240 241 i, err := strconv.Atoi(result) 242 if err != nil { 243 log.Warnf("Invalid environment variable value `%s`, expecting an integer, defaulting to %v", result, v.DefaultValue) 244 i, _ = strconv.Atoi(v.DefaultValue) 245 } 246 247 return i, ok 248} 249 250// Get retrieves the value of the environment variable. 251// It returns the value, which will be the default if the variable is not present. 252// To distinguish between an empty value and an unset value, use Lookup. 253func (v FloatVar) Get() float64 { 254 result, _ := v.Lookup() 255 return result 256} 257 258// Lookup retrieves the value of the environment variable. If the 259// variable is present in the environment the 260// value (which may be empty) is returned and the boolean is true. 261// Otherwise the returned value will be the default and the boolean will 262// be false. 263func (v FloatVar) Lookup() (float64, bool) { 264 result, ok := os.LookupEnv(v.Name) 265 if !ok { 266 result = v.DefaultValue 267 } 268 269 f, err := strconv.ParseFloat(result, 64) 270 if err != nil { 271 log.Warnf("Invalid environment variable value `%s`, expecting a floating-point value, defaulting to %v", result, v.DefaultValue) 272 f, _ = strconv.ParseFloat(v.DefaultValue, 64) 273 } 274 275 return f, ok 276} 277 278// Get retrieves the value of the environment variable. 279// It returns the value, which will be the default if the variable is not present. 280// To distinguish between an empty value and an unset value, use Lookup. 281func (v DurationVar) Get() time.Duration { 282 result, _ := v.Lookup() 283 return result 284} 285 286// Lookup retrieves the value of the environment variable. If the 287// variable is present in the environment the 288// value (which may be empty) is returned and the boolean is true. 289// Otherwise the returned value will be the default and the boolean will 290// be false. 291func (v DurationVar) Lookup() (time.Duration, bool) { 292 result, ok := os.LookupEnv(v.Name) 293 if !ok { 294 result = v.DefaultValue 295 } 296 297 d, err := time.ParseDuration(result) 298 if err != nil { 299 log.Warnf("Invalid environment variable value `%s`, expecting a duration, defaulting to %v", result, v.DefaultValue) 300 d, _ = time.ParseDuration(v.DefaultValue) 301 } 302 303 return d, ok 304} 305