Skip to content

Commit

Permalink
feat(exp): add envutils package (#466)
Browse files Browse the repository at this point in the history
Add a function to read value from the environment or from a file.
  • Loading branch information
jooola authored Jun 24, 2024
1 parent 1a55a7e commit a7636bd
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 0 deletions.
40 changes: 40 additions & 0 deletions hcloud/exp/kit/envutils/env.go
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
}
106 changes: 106 additions & 0 deletions hcloud/exp/kit/envutils/env_test.go
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)
})
}
}

0 comments on commit a7636bd

Please sign in to comment.