Skip to content

Unmarshal non-bound environment variables #761

Closed
@sagikazarmark

Description

This is going to be an umbrella issue for other open issues for this problem.

These are:

Problem

Currently Viper cannot unmarshal automatically loaded environment variables.

Here is a failing test case reproducing the issue:

package viper

import (
	"os"
	"testing"
)

func TestUnmarshal_AutomaticEnv(t *testing.T) {
	os.Setenv("MYKEY", "myvalue")

	type Config struct {
		MyKey string `mapstructure:"mykey"`
	}

	var c Config

	AutomaticEnv()

	if err := Unmarshal(&c); err != nil {
		t.Fatal(err)
	}

	if c.MyKey != "myvalue" {
		t.Error("failed to unmarshal automatically loaded environment variable")
	}

	os.Clearenv()
}

Root cause

Automatic environment variable loading works as follows:

  1. User requests a value from Viper: viper.GetString("mykey")
  2. Viper runs the env key replacer on the key
  3. Viper compares the key (case insensitively) with the list of environment variables
  4. Returns the value if one is found

Unmarshal, however, doesn't work by requesting a key, quite the opposite: it tries to unmarshal existing keys onto the given structure. Since automatically loaded environment variables are not in the list of existing keys (unlike defaults and bind env vars), these variables won't be unmarshaled.

Workaround

  1. Use BindEnv to manually bind environment variables
  2. Use SetDefault to set a default value (even an empty one), so Viper can find the key and try to find a matching env var

Possible solutions

The linked issues list a number of workarounds to fix this issue.

Personally, I'm in favor of the following:

  1. Unmarshal the given struct onto a map[string]interface{}. That will give use a nested structure of all the keys otherwise mapstructure expects.
  2. Flatten the structure, so we get a list of expected keys
  3. Check that every key is defined. If not, check if there is a matching environment variable.
  4. Merge the available settings with the matching, automatically loaded environment variables.
  5. Use mapstructure as usual

Although I think that this is currently the best available solution, it doesn't mean we can't do better, so please do suggest if you have an idea.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions