-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(exp): add envutils package (#466)
Add a function to read value from the environment or from a file.
- Loading branch information
Showing
2 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package envutils | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// LookupEnvWithFile retrieves the value of the environment variable named by the key (e.g. | ||
// HCLOUD_TOKEN). If the previous environment variable is not set, it retrieves the | ||
// content of the file located by a second environment variable named by the key + | ||
// '_FILE' (.e.g HCLOUD_TOKEN_FILE). | ||
// | ||
// For both cases, the returned value may be empty. | ||
// | ||
// The value from the environment takes precedence over the value from the file. | ||
func LookupEnvWithFile(key string) (string, error) { | ||
// Check if the value is set in the environment (e.g. HCLOUD_TOKEN) | ||
value, ok := os.LookupEnv(key) | ||
if ok { | ||
return value, nil | ||
} | ||
|
||
key += "_FILE" | ||
|
||
// Check if the value is set via a file (e.g. HCLOUD_TOKEN_FILE) | ||
valueFile, ok := os.LookupEnv(key) | ||
if !ok { | ||
// Validation of the value happens outside of this function | ||
return "", nil | ||
} | ||
|
||
// Read the content of the file | ||
valueBytes, err := os.ReadFile(valueFile) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read %s: %w", key, err) | ||
} | ||
|
||
return strings.TrimSpace(string(valueBytes)), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package envutils | ||
|
||
import ( | ||
"os" | ||
"path" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// nolint:unparam | ||
func writeTmpFile(t *testing.T, tmpDir, filename, content string) string { | ||
filepath := path.Join(tmpDir, filename) | ||
|
||
err := os.WriteFile(filepath, []byte(content), 0644) | ||
require.NoError(t, err) | ||
|
||
return filepath | ||
} | ||
|
||
func TestLookupEnvWithFile(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
setup func(t *testing.T, tmpDir string) | ||
want func(t *testing.T, value string, err error) | ||
}{ | ||
{ | ||
name: "without any environment", | ||
setup: func(_ *testing.T, _ string) {}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.NoError(t, err) | ||
assert.Equal(t, "", value) | ||
}, | ||
}, | ||
{ | ||
name: "value from environment", | ||
setup: func(t *testing.T, tmpDir string) { | ||
t.Setenv("CONFIG", "value") | ||
|
||
// Test for precedence | ||
filepath := writeTmpFile(t, tmpDir, "config", "content") | ||
t.Setenv("CONFIG_FILE", filepath) | ||
}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.NoError(t, err) | ||
assert.Equal(t, "value", value) | ||
}, | ||
}, | ||
{ | ||
name: "empty value from environment", | ||
setup: func(t *testing.T, tmpDir string) { | ||
t.Setenv("CONFIG", "") | ||
|
||
// Test for precedence | ||
filepath := writeTmpFile(t, tmpDir, "config", "content") | ||
t.Setenv("CONFIG_FILE", filepath) | ||
}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.NoError(t, err) | ||
assert.Equal(t, "", value) | ||
}, | ||
}, | ||
{ | ||
name: "value from file", | ||
setup: func(t *testing.T, tmpDir string) { | ||
// The extra spaces ensure that the value is sanitized | ||
filepath := writeTmpFile(t, tmpDir, "config", "content ") | ||
t.Setenv("CONFIG_FILE", filepath) | ||
}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.NoError(t, err) | ||
assert.Equal(t, "content", value) | ||
}, | ||
}, | ||
{ | ||
name: "empty value from file", | ||
setup: func(t *testing.T, tmpDir string) { | ||
filepath := writeTmpFile(t, tmpDir, "config", "") | ||
t.Setenv("CONFIG_FILE", filepath) | ||
}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.NoError(t, err) | ||
assert.Equal(t, "", value) | ||
}, | ||
}, | ||
{ | ||
name: "missing file", | ||
setup: func(t *testing.T, _ string) { | ||
t.Setenv("CONFIG_FILE", "/tmp/this-file-does-not-exits") | ||
}, | ||
want: func(t *testing.T, value string, err error) { | ||
assert.Error(t, err, "failed to read CONFIG_FILE: open /tmp/this-file-does-not-exits: no such file or directory") | ||
assert.Equal(t, "", value) | ||
}, | ||
}, | ||
} | ||
for _, testCase := range testCases { | ||
t.Run(testCase.name, func(t *testing.T) { | ||
tmpDir := t.TempDir() | ||
testCase.setup(t, tmpDir) | ||
value, err := LookupEnvWithFile("CONFIG") | ||
testCase.want(t, value, err) | ||
}) | ||
} | ||
} |