-
Notifications
You must be signed in to change notification settings - Fork 8
/
envvar.go
242 lines (213 loc) · 8.89 KB
/
envvar.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package environment
import (
"fmt"
"regexp"
"sort"
"strings"
validation "github.com/go-ozzo/ozzo-validation/v4"
"golang.org/x/exp/maps"
"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/platform"
)
var (
envvarKeyRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") // See [IEEE Std 1003.1-2008 / IEEE POSIX P1003.2/ISO 9945.2](http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10_02)
errEnvvarInvalid = validation.NewError("validation_is_environment_variable", "must be a valid Posix environment variable")
// IsEnvironmentVariableKey defines a validation rule for environment variable keys ([IEEE Std 1003.1-2008 / IEEE POSIX P1003.2/ISO 9945.2](http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10_02)) for use with github.com/go-ozzo/ozzo-validation
// TODO use the built-in implementation in `is` package when https://github.com/go-ozzo/ozzo-validation/issues/186 is looked at.
IsEnvironmentVariableKey = validation.NewStringRuleWithError(isEnvVarKey, errEnvvarInvalid)
)
type EnvVar struct {
key string
value string
validationRules []validation.Rule
}
func (e *EnvVar) MarshalText() (text []byte, err error) {
err = e.Validate()
text = []byte(e.String())
return
}
func (e *EnvVar) UnmarshalText(text []byte) error {
env, err := ParseEnvironmentVariable(string(text))
if err != nil {
return err
}
e.key = env.GetKey()
e.value = env.GetValue()
return nil
}
func (e *EnvVar) Equal(v IEnvironmentVariable) bool {
if v == nil {
return e == nil
}
if e == nil {
return false
}
return e.GetKey() == v.GetKey() && e.GetValue() == v.GetValue()
}
func (e *EnvVar) GetKey() string {
return e.key
}
func (e *EnvVar) GetValue() string {
return e.value
}
func (e *EnvVar) String() string {
return fmt.Sprintf("%v=%v", e.GetKey(), e.GetValue())
}
func (e *EnvVar) Validate() (err error) {
err = validation.Validate(e.GetKey(), validation.Required, IsEnvironmentVariableKey)
if err != nil {
err = fmt.Errorf("%w: environment variable name `%v` is not valid: %v", commonerrors.ErrInvalid, e.GetKey(), err.Error())
return
}
if len(e.validationRules) > 0 {
err = validation.Validate(e.GetValue(), e.validationRules...)
if err != nil {
err = fmt.Errorf("%w: environment variable `%v` value is not valid: %v", commonerrors.ErrInvalid, e.GetKey(), err.Error())
}
}
return
}
func isEnvVarKey(value string) bool {
// FIXME remove when supported by the validation tool see https://github.com/go-ozzo/ozzo-validation/issues/186
return envvarKeyRegex.MatchString(value)
}
// ParseEnvironmentVariable parses an environment variable definition, in the form "key=value".
func ParseEnvironmentVariable(variable string) (IEnvironmentVariable, error) {
elements := strings.Split(strings.TrimSpace(variable), "=")
if len(elements) < 2 {
return nil, fmt.Errorf("%w: invalid environment variable entry as not following key=value", commonerrors.ErrInvalid)
}
value := elements[1]
if len(elements) > 2 {
var valueElems []string
for i := 1; i < len(elements); i++ {
valueElems = append(valueElems, elements[i])
}
value = strings.Join(valueElems, "=")
}
envvar := NewEnvironmentVariable(elements[0], value)
return envvar, envvar.Validate()
}
// NewEnvironmentVariable returns an environment variable defined by a key and a value.
func NewEnvironmentVariable(key, value string) IEnvironmentVariable {
return NewEnvironmentVariableWithValidation(key, value)
}
// CloneEnvironmentVariable returns a clone of the environment variable.
func CloneEnvironmentVariable(envVar IEnvironmentVariable) IEnvironmentVariable {
if envVar == nil {
return nil
}
return NewEnvironmentVariable(envVar.GetKey(), envVar.GetValue())
}
// NewEnvironmentVariableWithValidation returns an environment variable defined by a key and a value but with the possibility to define value validation rules.
func NewEnvironmentVariableWithValidation(key, value string, rules ...validation.Rule) IEnvironmentVariable {
return &EnvVar{
key: key,
value: value,
validationRules: rules,
}
}
// ValidateEnvironmentVariables validates that environment variables are correctly defined in regard to their schema.
func ValidateEnvironmentVariables(vars ...IEnvironmentVariable) error {
for i := range vars {
err := vars[i].Validate()
if err != nil {
return err
}
}
return nil
}
// ParseEnvironmentVariables parses a list of key=value entries such as os.Environ() and returns a list of the corresponding environment variables.
// Any entry failing parsing will be ignored.
func ParseEnvironmentVariables(variables ...string) (envVars []IEnvironmentVariable) {
for i := range variables {
envvar, err := ParseEnvironmentVariable(variables[i])
if err != nil {
continue
}
envVars = append(envVars, envvar)
}
return
}
// FindEnvironmentVariable looks for an environment variable in a list. if no environment variable matches, an error is returned
func FindEnvironmentVariable(envvar string, envvars ...IEnvironmentVariable) (IEnvironmentVariable, error) {
for i := range envvars {
if envvars[i].GetKey() == envvar {
return envvars[i], nil
}
}
return nil, fmt.Errorf("%w: environment variable '%v' not set", commonerrors.ErrNotFound, envvar)
}
// FindFoldEnvironmentVariable looks for an environment variable in a list similarly to FindEnvironmentVariable but without case-sensitivity.
func FindFoldEnvironmentVariable(envvar string, envvars ...IEnvironmentVariable) (IEnvironmentVariable, error) {
for i := range envvars {
if strings.EqualFold(envvars[i].GetKey(), envvar) {
return envvars[i], nil
}
}
return nil, fmt.Errorf("%w: environment variable '%v' not set", commonerrors.ErrNotFound, envvar)
}
// ExpandEnvironmentVariables returns a list of environment variables with their value being expanded.
// Expansion assumes that all the variables are present in the envvars list.
// If recursive is set to true, then expansion is performed recursively over the variable list.
func ExpandEnvironmentVariables(recursive bool, envvars ...IEnvironmentVariable) (expandedEnvVars []IEnvironmentVariable) {
for i := range envvars {
expandedEnvVars = append(expandedEnvVars, ExpandEnvironmentVariable(recursive, envvars[i], envvars...))
}
return
}
// ExpandEnvironmentVariable returns a clone of envVarToExpand but with an expanded value based on environment variables defined in envvars list.
// Expansion assumes that all the variables are present in the envvars list.
// If recursive is set to true, then expansion is performed recursively over the variable list.
func ExpandEnvironmentVariable(recursive bool, envVarToExpand IEnvironmentVariable, envvars ...IEnvironmentVariable) (expandedEnvVar IEnvironmentVariable) {
if len(envvars) == 0 || envVarToExpand == nil {
return envVarToExpand
}
mappingFunc := func(envvarKey string) (string, bool) {
envVar, err := FindEnvironmentVariable(envvarKey, envvars...)
if commonerrors.Any(err, commonerrors.ErrNotFound) {
return "", false
}
return envVar.GetValue(), true
}
expandedEnvVar = NewEnvironmentVariable(envVarToExpand.GetKey(), platform.ExpandParameter(envVarToExpand.GetValue(), mappingFunc, recursive))
return
}
// UniqueEnvironmentVariables returns a list of unique environment variables.
// caseSensitive states whether two same keys but with different case should be both considered unique.
func UniqueEnvironmentVariables(caseSensitive bool, envvars ...IEnvironmentVariable) (uniqueEnvVars []IEnvironmentVariable) {
uniqueSet := map[string]IEnvironmentVariable{}
recordUniqueEnvVar(caseSensitive, envvars, uniqueSet)
uniqueEnvVars = maps.Values(uniqueSet)
return
}
// SortEnvironmentVariables sorts a list of environment variable alphabetically no matter the case.
func SortEnvironmentVariables(envvars []IEnvironmentVariable) {
if len(envvars) == 0 {
return
}
sort.SliceStable(envvars, func(i, j int) bool {
return strings.ToLower(envvars[i].GetKey()) < strings.ToLower(envvars[j].GetKey())
})
}
// MergeEnvironmentVariableSets merges two sets of environment variables.
// If both sets have a same environment variable, its value in set 1 will take precedence.
// caseSensitive states whether two similar keys with different case should be considered as different
func MergeEnvironmentVariableSets(caseSensitive bool, envvarSet1 []IEnvironmentVariable, envvarSet2 ...IEnvironmentVariable) (mergedEnvVars []IEnvironmentVariable) {
mergeSet := map[string]IEnvironmentVariable{}
recordUniqueEnvVar(caseSensitive, envvarSet1, mergeSet)
recordUniqueEnvVar(caseSensitive, envvarSet2, mergeSet)
mergedEnvVars = maps.Values(mergeSet)
return
}
func recordUniqueEnvVar(caseSensitive bool, envvarSet []IEnvironmentVariable, hashTable map[string]IEnvironmentVariable) {
for i := range envvarSet {
key := envvarSet[i].GetKey()
if !caseSensitive {
key = strings.ToLower(key)
}
if _, contains := hashTable[key]; !contains {
hashTable[key] = envvarSet[i]
}
}
}