Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package ejson2env
import (
"errors"
"fmt"
"regexp"
)

var errNoEnv = errors.New("environment is not set in ejson")
var errEnvNotMap = errors.New("environment is not a map[string]interface{}")

var validIdentifierPattern = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also work, and would be shorter

Suggested change
var validIdentifierPattern = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
var validIdentifierPattern = regexp.MustCompile(`\A[a-zA-Z_]\w*\z`)

but after thinking about it, I think the long form is clearer, as it explicitly shows how the first character can't be a digit, but that otherwise it's all the same.


// ExtractEnv extracts the environment values from the map[string]interface{}
// containing all secrets, and returns a map[string]string containing the
// key value pairs. If there's an issue (the environment key doesn't exist, for
Expand All @@ -26,6 +29,12 @@ func ExtractEnv(secrets map[string]interface{}) (map[string]string, error) {
envSecrets := make(map[string]string, len(envMap))

for key, rawValue := range envMap {
// Reject keys that would be invalid environment variable identifiers
if !validIdentifierPattern.MatchString(key) {
err := fmt.Errorf("invalid identifier as key in environment: %q", key)

return nil, err
}
Comment on lines +32 to +37
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the comment below, we seem to ignore when the values don't convert to strings properly, rather than return an error. IMO we should be loud about these failures (I think non-strings should also be errors, or at least print something to stderr), however if we really think it's better to silently continue, then we could change this to "only export keys that are valid identifiers" (and simply ignore invalid ones).

Separately, I may open a PR to add said stderr message (since making it an error would be a breaking change that could break production deployments).


// Only export values that convert to strings properly.
if value, ok := rawValue.(string); ok {
Expand Down
67 changes: 61 additions & 6 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func formatInvalid(received, expected string) string {

func TestLoadSecrets(t *testing.T) {

rawValues, err := readSecrets("testdata/test.ejson", "./key", TestKeyValue)
rawValues, err := readSecrets("testdata/test-expected-usage.ejson", "./key", TestKeyValue)
if nil != err {
t.Fatal(err)
}
Expand All @@ -31,7 +31,7 @@ func TestLoadSecrets(t *testing.T) {

func TestLoadNoEnvSecrets(t *testing.T) {

rawValues, err := readSecrets("testdata/test2.ejson", "./key", TestKeyValue)
rawValues, err := readSecrets("testdata/test-public-key-only.ejson", "./key", TestKeyValue)
if nil != err {
t.Fatal(err)
}
Expand All @@ -49,7 +49,7 @@ func TestLoadNoEnvSecrets(t *testing.T) {

func TestLoadBadEnvSecrets(t *testing.T) {

rawValues, err := readSecrets("testdata/test3.ejson", "./key", TestKeyValue)
rawValues, err := readSecrets("testdata/test-environment-string-not-object.ejson", "./key", TestKeyValue)
if nil != err {
t.Fatal(err)
}
Expand All @@ -67,7 +67,7 @@ func TestLoadBadEnvSecrets(t *testing.T) {

func TestLoadUnderscoreEnvSecrets(t *testing.T) {

rawValues, err := readSecrets("testdata/test4.ejson", "./key", TestKeyValue)
rawValues, err := readSecrets("testdata/test-leading-underscore-env-key.ejson", "./key", TestKeyValue)
if nil != err {
t.Fatal(err)
}
Expand All @@ -90,19 +90,32 @@ func TestInvalidEnvironments(t *testing.T) {
},
}

testBad := map[string]interface{}{
testBadNonMap := map[string]interface{}{
"environment": "bad",
}

testBadInvalidKey := map[string]interface{}{
"environment": map[string]interface{}{
"invalid key": "test_value",
},
}

var testNoEnv map[string]interface{}

_, err := ExtractEnv(testBad)
_, err := ExtractEnv(testBadNonMap)
if nil == err {
t.Errorf("no error when passed a non-map environment")
} else if errEnvNotMap != err {
t.Errorf("wrong error when passed a non-map environment: %s", err)
}

_, err = ExtractEnv(testBadInvalidKey)
if nil == err {
t.Errorf("no error when passed an environment with invalid key")
} else if `invalid identifier as key in environment: "invalid key"` != err.Error() {
t.Errorf("wrong error when passed an environment with invalid key: %s", err)
}

_, err = ExtractEnv(testNoEnv)
if nil == err {
t.Errorf("no error when passed a non-existent environment")
Expand Down Expand Up @@ -133,3 +146,45 @@ func TestEscaping(t *testing.T) {
}

}

func TestIdentifierPattern(t *testing.T) {
key := "ALL_CAPS123"
if !validIdentifierPattern.MatchString(key) {
t.Errorf("key should match pattern %q: %q", validIdentifierPattern, key)
}

key = "lowercase"
if !validIdentifierPattern.MatchString(key) {
t.Errorf("key should match pattern %q: %q", validIdentifierPattern, key)
}

key = "a"
if !validIdentifierPattern.MatchString(key) {
t.Errorf("key should match pattern %q: %q", validIdentifierPattern, key)
}

key = "_leading_underscore"
if !validIdentifierPattern.MatchString(key) {
t.Errorf("key should match pattern %q: %q", validIdentifierPattern, key)
}

key = "1_leading_digit"
if validIdentifierPattern.MatchString(key) {
t.Errorf("key should not match pattern %q: %q", validIdentifierPattern, key)
}

key = "contains whitespace"
if validIdentifierPattern.MatchString(key) {
t.Errorf("key should not match pattern %q: %q", validIdentifierPattern, key)
}

key = "contains-dash"
if validIdentifierPattern.MatchString(key) {
t.Errorf("key should not match pattern %q: %q", validIdentifierPattern, key)
}

key = "contains_special_character;"
if validIdentifierPattern.MatchString(key) {
t.Errorf("key should not match pattern %q: %q", validIdentifierPattern, key)
}
}
2 changes: 1 addition & 1 deletion secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestReadAndExportEnv(t *testing.T) {
}

for _, test := range tests {
err := ReadAndExportEnv("testdata/test.ejson", "./key", TestKeyValue, test.exportFunc)
err := ReadAndExportEnv("testdata/test-expected-usage.ejson", "./key", TestKeyValue, test.exportFunc)
if nil != err {
t.Errorf("testing %s failed: %s", test.name, err)
continue
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.