Skip to content

Golang library for managing configuration data from environment variables

License

Notifications You must be signed in to change notification settings

colega/envconfig

 
 

Repository files navigation

envconfig

Build Status Coverage Status GoDoc

import "github.com/colega/envconfig"

A fork of github.com/kelseyhightower/envconfig with more features and less support for older versions. See fork compatibility for more details.

Usage

Set some environment variables:

export MYAPP_DEBUG=false
export MYAPP_PORT=8080
export MYAPP_USER=Kelsey
export MYAPP_RATE="0.5"
export MYAPP_TIMEOUT="3m"
export MYAPP_USERS="rob,ken,robert"
export MYAPP_COLORCODES="red:1,green:2,blue:3"
export MYAPP_DB_0_HOST="mysql-1.default"
export MYAPP_DB_1_HOST="mysql-2.default"

Write some code:

import (
	"fmt"
	"log"
	"time"

	"github.com/colega/envconfig"
)

type Specification struct {
	Debug      bool
	Port       int
	User       string
	Users      []string
	Rate       float32
	Timeout    time.Duration
	ColorCodes map[string]int
	DB         []struct {
		Host string
		Port int `default:"3306"`
	}
}

func main() {
	var s Specification
	err := envconfig.Process("myapp", &s)
	if err != nil {
		log.Fatal(err.Error())
	}
	format := "Debug: %v\nPort: %d\nUser: %s\nRate: %f\nTimeout: %s\n"
	_, err = fmt.Printf(format, s.Debug, s.Port, s.User, s.Rate, s.Timeout)
	if err != nil {
		log.Fatal(err.Error())
	}

	fmt.Println("Users:")
	for _, u := range s.Users {
		fmt.Printf("  %s\n", u)
	}

	fmt.Println("Color codes:")
	for k, v := range s.ColorCodes {
		fmt.Printf("  %s: %d\n", k, v)
	}

	fmt.Println("Databases:")
	for i, db := range s.DB {
		fmt.Printf("  %d: %s:%d\n", i, db.Host, db.Port)
	}
}

Results:

Debug: false
Port: 8080
User: Kelsey
Rate: 0.500000
Timeout: 3m0s
Users:
  rob
  ken
  robert
Color codes:
  green: 2
  blue: 3
  red: 1
Databases:
  0: mysql-1.default:3306
  1: mysql-2.default:3306

Struct Tag Support

Envconfig supports the use of struct tags to specify alternate, default, and required environment variables.

For example, consider the following struct:

type Specification struct {
	ManualOverride1         string `envconfig:"manual_override_1"`
	DefaultVar              string `default:"foobar"`
	RequiredVar             string `required:"true"`
	IgnoredVar              string `ignored:"true"`
	AutoSplitVar            string `split_words:"true"`
	RequiredAndAutoSplitVar string `required:"true" split_words:"true"`
}

Envconfig has automatic support for CamelCased struct elements when the split_words:"true" tag is supplied. Without this tag, AutoSplitVar above would look for an environment variable called MYAPP_AUTOSPLITVAR. With the setting applied it will look for MYAPP_AUTO_SPLIT_VAR. Note that numbers will get globbed into the previous word. If the setting does not do the right thing, you may use a manual override.

Envconfig will process value for ManualOverride1 by populating it with the value for MYAPP_MANUAL_OVERRIDE_1. Without this struct tag, it would have instead looked up MYAPP_MANUALOVERRIDE1. With the split_words:"true" tag it would have looked up MYAPP_MANUAL_OVERRIDE1.

export MYAPP_MANUAL_OVERRIDE_1="this will be the value"

# export MYAPP_MANUALOVERRIDE1="and this will not"

If envconfig can't find an environment variable value for MYAPP_DEFAULTVAR, it will populate it with "foobar" as a default value.

If envconfig can't find an environment variable value for MYAPP_REQUIREDVAR, it will return an error when asked to process the struct. If MYAPP_REQUIREDVAR is present but empty, envconfig will not return an error.

If envconfig can't find an environment variable in the form PREFIX_MYVAR, and there is a struct tag defined, it will try to populate your variable with an environment variable that directly matches the envconfig tag in your struct definition:

export SERVICE_HOST=127.0.0.1
export MYAPP_DEBUG=true
type Specification struct {
	ServiceHost string `envconfig:"SERVICE_HOST"`
	Debug       bool
}

Envconfig won't process a field with the "ignored" tag set to "true", even if a corresponding environment variable is set.

Unused fields detection

Unused(prefix string, spec interface{}) ([]string, error) provides a slice of environment variables with the given prefix that are not parsed by the spec. This is a more versatile replacement for envconfig.CheckDisallowed from the original project. Useful to report unused variables to your metrics system (set a prometheus gauge for each of the unused variables and visualize them in Grafana?) or logging system, as well as for validating config and failing (len(unused) > 0) if there are unexpected config variables (which most likely are typos os wrong configuration version). Check the examples for an example usage.

Supported Struct Field Types

envconfig supports these struct field types:

Embedded structs using these fields are also supported.

Slices of structs

Envconfig supports slices of structs in the following form:

export MYAPP_INNER_0_VALUE=hello
export MYAPP_INNER_1_VALUE=world
type Specification struct {
	Inner []struct {
		Value string
	}
}

The slice itself can be tagged with envconfig tag and works in the usual way, however, envconfig tag will be used only to overwrite the key name and won't be used to fill the value from a matching non-prefixed global variable (because all the slice elements share the same tag)

For example:

export MYAPP_ALTERNATIVE_0_KEY=hello
export MYAPP_ALTERNATIVE_1_KEY=world
type Specification struct {
	Inner []struct {
		Value string `envconfig:"key"`
	} `envconfig:"alternative"`
}

and:

export ALTERNATIVE_0_KEY=hello
export ALTERNATIVE_1_KEY=world
type Specification struct {
	Inner []struct {
		Value string `envconfig:"key"`
	} `envconfig:"alternative"`
}

But in the following example, KEY will be ignored, although it's an unprefixed version of an envconfig tag:

export KEY=THIS_WILL_BE_IGNORED
type IgnoredTagSpecification struct {
	Inner []struct {
		Value string `envconfig:"key"`
	}
}

Custom Decoders

Any field whose type (or pointer-to-type) implements envconfig.Decoder can control its own deserialization:

export DNS_SERVER=8.8.8.8
type IPDecoder net.IP

func (ipd *IPDecoder) Decode(value string) error {
	*ipd = IPDecoder(net.ParseIP(value))
	return nil
}

type DNSConfig struct {
	Address IPDecoder `envconfig:"DNS_SERVER"`
}

Also, envconfig will use a Set(string) error method like from the flag.Value interface if implemented.

Fork compatibility

This fork maintains interface and tag compatibility with github.com/kelseyhightower/envconfig@v1.5.0, i.e., entities implementing same interfaces and defining same tags work the same way with github.com/colega/envconfig.

However, since it's defined as a different package, it doesn't share the versioning and thus doesn't offer the package-compatibility: for instance CheckDisallowed() is not present in this package and is replaced by Unused() function. It doesn't make sense to try to make this package a drop-in replacement (using go.mod's replace directives) since its go.mod defines a different package anyway.

About

Golang library for managing configuration data from environment variables

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 100.0%