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