Skip to content

Commit 6082ac7

Browse files
authored
Support default values and erorrs for env var expansion (#10907)
#### Description Support shell-style default value and error message for env var expansion. ``` // A default value for unset variable can be provided after :- suffix, for example: // `env:NAME_OF_ENVIRONMENT_VARIABLE:-default_value` ``` #### Link to tracking issue Fixes #5228 #### Testing Unit tests #### Documentation * [x] Provider Go docs * [ ] Probably needs some other docs changes? --------- Signed-off-by: Yuri Shkuro <[email protected]>
1 parent 02d466f commit 6082ac7

File tree

3 files changed

+83
-3
lines changed

3 files changed

+83
-3
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: confmap/provider/envprovider
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Support default values when env var is empty
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [5228]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: ['user']

confmap/provider/envprovider/provider.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ type provider struct {
2727
//
2828
// This Provider supports "env" scheme, and can be called with a selector:
2929
// `env:NAME_OF_ENVIRONMENT_VARIABLE`
30+
//
31+
// A default value for unset variable can be provided after :- suffix, for example:
32+
// `env:NAME_OF_ENVIRONMENT_VARIABLE:-default_value`
33+
//
34+
// See also: https://opentelemetry.io/docs/specs/otel/configuration/file-configuration/#environment-variable-substitution
3035
func NewFactory() confmap.ProviderFactory {
3136
return confmap.NewProviderFactory(newProvider)
3237
}
@@ -41,14 +46,18 @@ func (emp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFu
4146
if !strings.HasPrefix(uri, schemeName+":") {
4247
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
4348
}
44-
envVarName := uri[len(schemeName)+1:]
49+
envVarName, defaultValuePtr := parseEnvVarURI(uri[len(schemeName)+1:])
4550
if !envvar.ValidationRegexp.MatchString(envVarName) {
4651
return nil, fmt.Errorf("environment variable %q has invalid name: must match regex %s", envVarName, envvar.ValidationPattern)
47-
4852
}
53+
4954
val, exists := os.LookupEnv(envVarName)
5055
if !exists {
51-
emp.logger.Warn("Configuration references unset environment variable", zap.String("name", envVarName))
56+
if defaultValuePtr != nil {
57+
val = *defaultValuePtr
58+
} else {
59+
emp.logger.Warn("Configuration references unset environment variable", zap.String("name", envVarName))
60+
}
5261
} else if len(val) == 0 {
5362
emp.logger.Info("Configuration references empty environment variable", zap.String("name", envVarName))
5463
}
@@ -63,3 +72,13 @@ func (*provider) Scheme() string {
6372
func (*provider) Shutdown(context.Context) error {
6473
return nil
6574
}
75+
76+
// returns (var name, default value)
77+
func parseEnvVarURI(uri string) (string, *string) {
78+
const defaultSuffix = ":-"
79+
if strings.Contains(uri, defaultSuffix) {
80+
parts := strings.SplitN(uri, defaultSuffix, 2)
81+
return parts[0], &parts[1]
82+
}
83+
return uri, nil
84+
}

confmap/provider/envprovider/provider_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,42 @@ func TestEmptyEnvWithLoggerWarn(t *testing.T) {
154154
assert.Equal(t, envName, logLine.Context[0].String)
155155
}
156156

157+
func TestEnvWithDefaultValue(t *testing.T) {
158+
env := createProvider()
159+
tests := []struct {
160+
name string
161+
unset bool
162+
value string
163+
uri string
164+
expectedVal string
165+
expectedErr string
166+
}{
167+
{name: "unset", unset: true, uri: "env:MY_VAR:-default % value", expectedVal: "default % value"},
168+
{name: "unset2", unset: true, uri: "env:MY_VAR:-", expectedVal: ""}, // empty default still applies
169+
{name: "empty", value: "", uri: "env:MY_VAR:-foo", expectedVal: ""},
170+
{name: "not empty", value: "value", uri: "env:MY_VAR:-", expectedVal: "value"},
171+
{name: "syntax1", unset: true, uri: "env:-MY_VAR", expectedErr: "invalid name"},
172+
{name: "syntax2", unset: true, uri: "env:MY_VAR:-test:-test", expectedVal: "test:-test"},
173+
}
174+
for _, test := range tests {
175+
t.Run(test.name, func(t *testing.T) {
176+
if !test.unset {
177+
t.Setenv("MY_VAR", test.value)
178+
}
179+
ret, err := env.Retrieve(context.Background(), test.uri, nil)
180+
if test.expectedErr != "" {
181+
require.ErrorContains(t, err, test.expectedErr)
182+
return
183+
}
184+
require.NoError(t, err)
185+
str, err := ret.AsString()
186+
require.NoError(t, err)
187+
assert.Equal(t, test.expectedVal, str)
188+
})
189+
}
190+
assert.NoError(t, env.Shutdown(context.Background()))
191+
}
192+
157193
func createProvider() confmap.Provider {
158194
return NewFactory().Create(confmaptest.NewNopProviderSettings())
159195
}

0 commit comments

Comments
 (0)