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  }