envcfg

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2021 License: MIT Imports: 12 Imported by: 0

README

envcfg Build Status

envcfg is a Go package for loading config from environment variables into struct fields of arbitrary types. It's designed with a few guiding principles:

  1. If there's a bug in your config values, it's far better to see it right when your application starts than at 3 AM Sunday morning when your app finally gets around to using that value. So handlers and other business logic components shouldn't read or parse env vars themselves; they should access config that's already been read and parsed into concrete Go types for them.
  2. It's nice to have one centralized place where it's easy to see all the config that your app (or component) needs.
  3. Loading config should require as little boilerplate as possible.

envcfg is inspired by the struct tag pattern used by Go when unmarshaling JSON or scanning database rows. .

Simple Example

Imagine you had a struct that held your app's config values, that looked something like this:

  type myAppConfig struct {
    Foo             string       
    Bar             int          
    RefreshInterval time.Duration
  }

To load those values from environment variables, you would add struct tags for env and optionally default, and then call envcfg.Load with a pointer to an instance of that config struct, like this:

    type myAppConfig struct {
      Foo             string        `env:"FOO" default:"hey there"`
      Bar             int           `env:"BAR"`
      RefreshInterval time.Duration `env:"REFRESH_INTERVAL" default:"2h30m"`
    }

    var conf myAppConfig
    err := envcfg.Load(&conf)
    if err != nil {
      panic(err.Error())
    }

    // now start up your app with your nicely-populated config...

In the example above, our config object has a string, an int, and a time.Duration. It requires that there be environment variables set for "BAR" and "REFRESH_RATE". If those aren't set, then envcfg.Load will return an error. The FOO environment variable may also be set, but the default of "hey there" will be used if not.

Built-in Supported Types

As demonstrated in the above example, envcfg already knows how to parse strings into many of the types built in to Go and its standard library. Here's the complete list:

int
bool              
string            
float32           
float64           
int8              
int16             
int32             
int64             
uint              
uint8             
uint16            
uint32            
uint64            
time.Duration     
time.Time         
*url.URL          
net.IP
net.HardwareAddr  
*mail.Address     
[]*mail.Address   
*template.Template

Parsing Other Types

If your struct has a field of some other type, you can tell envcfg how to parse a string into it by registering your own parser function. You can register parsers for struct types, pointers to struct types, arrays, and type aliases like type MyInt int. The example below demonstrates registering a custom parser function that will populate a *sql.DB field on a struct.

type myAppConfig struct {
  DB              *sql.DB       `env:"DATABASE_URL"`
}

func main() {
  // to load config we need to instantiate our config struct and pass its pointer to envcfg.Load
  var conf myAppConfig
  err := envcfg.Load(&conf)
  if err != nil {
    fmt.Println(err)
  }
  // now do something useful with the DB connection we just set up...
}

func LoadDBConnection(s string) (*sql.DB, error) {
  db, err := sql.Open("postgres", s)
  if err != nil {
    return nil, err
  }
  return db, nil
}

func init() {
  // A parser func takes one or more strings and returns the type matching your struct field,
  // and an error.
  if err := envcfg.RegisterParser(LoadDBConnection); err != nil {
    panic(err.Error())
  }
}

Loading a Single Field from Multiple Environment Variables

If you have a struct field that should be loaded from multiple environment variables, you can define a parser function that takes several string arguments. The env tag on your config struct field must then provide the same number of environment variable names to be passed to the parser (separated by commas). Here's an example that loads an Amazon S3 client from three commonly-used environment variables:

package main

import (
	"fmt"
	"io/ioutil"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/nav-inc/envcfg"
)

type myAppConfig struct {
	S3 *s3.S3 `env:"AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGION"`
}

func main() {
	var conf myAppConfig
	err := envcfg.Load(&conf)
	if err != nil {
		panic(err.Error())
	}
  // now the "conf" object has an S3 client on it that you can use to get/post files.
}

// LoadS3Client takes an access key id, secret, and default region, and returns an Amazon S3 client
// instance.
func LoadS3Client(key, secret, region string) (*s3.S3, error) {
	awsCreds := credentials.NewStaticCredentials(key, secret, "")
	_, err := awsCreds.Get()
	if err != nil {
		return nil, fmt.Errorf("bad AWS credentials: %s", err.Error())
	}
	awsCfg := aws.NewConfig().WithCredentials(awsCreds).WithRegion(region).WithS3ForcePathStyle(true)
	sess, err := session.NewSession()
	if err != nil {
		return nil, err
	}
	return s3.New(sess, awsCfg), nil
}

func init() {
	if err := envcfg.RegisterParser(LoadS3Client); err != nil {
		panic(err.Error())
	}
}

Using a Map Instead of Environment Variables

If you want to provide your own map of values instead of reading environment variables, there's also a envcfg.LoadFromMap function that will accept your own map[string]string.

myVars := map[string]string{
  "FOO": "Let's configure this",
  "BAR": "1",
  "DB":  "postgres://postgres@/my_app?sslmode=disable",
}

...

err := envcfg.LoadFromMap(myVars, &conf)

Instantiating Custom/Multiple Loaders

The examples above all use the default loader provided by the envcfg package. If you want more control you can instantiate your own loader (or multiple loaders) using envcfg.New():

    ec, err := envcfg.New()
    if err != nil {
      return err
    }

    err = ec.Load(&conf)

If you want a loader without any of the default parsers registered, you can get one by calling envcfg.Empty():

    ec := envcfg.Empty()
    err := ec.RegisterParser(myParserFunc)
    if err != nil {
      return err
    }

    err = ec.Load(&conf)

Comparison to github.com/kelseyhightower/envconfig

The day after I wrote the first version of this library, a friend pointed out the similar envconfig library from Kelsey Hightower. The world is big enough for both. There are a few differences that may make you prefer one over the other.

This library (envcfg) lets you register parsers for other people's types without needing to alias them to add UnmarshalText or Decode methods.

envcfg supports loading one field from multiple environment variables, while envconfig does not.

envconfig's support for Set(string) error methods (like those in flag.Value) should allow you to have struct fields that can be set from env vars or command line flags. There is no such support in envcfg.

If you don't designate the environment variable to use in a struct tag, the envconfig library will use the field's capitalization to guess at the environment variable to use. envcfg, on the other hand, will only attempt to load fields with explicit env tags, and it requires that either the environment variable or a default tag (or both) be set.

envconfig can load any type that implements the TextUnmarshaler or BinaryUnmarshaler interfaces. Many stdlib types implement one of these. envcfg should do that too, but hasn't yet. This would allow it to delete a bunch of code that registers parser funcs for stdlib types.

Documentation

Overview

Package envcfg provides helpers for reading values from environment variables (or a map[string]string), converting them to Go types, and setting their values to fields on a user-defined struct.

Example
package main

import (
	"database/sql"
	"fmt"
	"os"
	"time"

	_ "github.com/lib/pq"
	"github.com/nav-inc/envcfg"
)

func main() {
	// In a real app, these would already be set by your environment.
	os.Setenv("BAR", "321")
	os.Setenv("DATABASE_URL", "postgres://postgres@/my_app?sslmode=disable")

	type myAppConfig struct {
		Foo             string        `env:"FOO" default:"hey there"`
		Bar             int           `env:"BAR"`
		DB              *sql.DB       `env:"DATABASE_URL"`
		RefreshInterval time.Duration `env:"REFRESH_INTERVAL" default:"2h30m"`
	}

	// envcfg has built in support for many of Go's built in types, but not *sql.DB, so we'll have to
	// register our own parser.  A parser func takes a string and returns the type matching your
	// struct field, and an error.
	err := envcfg.RegisterParser(func(s string) (*sql.DB, error) {
		db, err := sql.Open("postgres", s)
		if err != nil {
			return nil, err
		}
		return db, nil
	})

	if err != nil {
		panic(err)
	}

	// to load config we need to instantiate our config struct and pass its pointer to envcfg.Load
	var conf myAppConfig
	err = envcfg.Load(&conf)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("Foo", conf.Foo)
	fmt.Println("Bar", conf.Bar)
	fmt.Println("Refresh Interval", conf.RefreshInterval)
}
Output:

Foo hey there
Bar 321
Refresh Interval 2h30m0s

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ParseBool             = strconv.ParseBool
	ParseInt              = strconv.Atoi
	ParseDuration         = time.ParseDuration
	ParseURL              = url.Parse
	ParseMAC              = net.ParseMAC
	ParseEmailAddress     = mail.ParseAddress
	ParseEmailAddressList = mail.ParseAddressList
	ParseTemplate         = template.New("").Parse
)

The default parsers are functions for converting strings into basic built-in Go types. These are exported so users can choose to start with a envcfg.Empty() and then pick and choose which of these parsers they want to register. A few types have been omitted: rune (use int32 instead), byte (use uint8 instead), uintptr, complex64, and complex128.

Functions

func Load

func Load(c interface{}) error

Load loads config from the environment into the provided struct.

func LoadFromMap

func LoadFromMap(vals map[string]string, c interface{}) error

LoadFromMap loads config from the provided map into the provided struct.

func MustRegisterParser added in v1.0.0

func MustRegisterParser(f interface{})

MustRegisterParser attempts to register the provided parser func and panics if it gets an error.

func ParseBytes added in v1.0.2

func ParseBytes(s string) ([]byte, error)

func ParseFloat32

func ParseFloat32(s string) (float32, error)

func ParseFloat64

func ParseFloat64(s string) (float64, error)

func ParseIP

func ParseIP(s string) (net.IP, error)

func ParseInt16

func ParseInt16(s string) (int16, error)

func ParseInt32

func ParseInt32(s string) (int32, error)

func ParseInt64

func ParseInt64(s string) (int64, error)

func ParseInt8

func ParseInt8(s string) (int8, error)

func ParseString

func ParseString(s string) (string, error)

func ParseTime

func ParseTime(s string) (time.Time, error)

func ParseUint

func ParseUint(s string) (uint, error)

func ParseUint16

func ParseUint16(s string) (uint16, error)

func ParseUint32

func ParseUint32(s string) (uint32, error)

func ParseUint64

func ParseUint64(s string) (uint64, error)

func ParseUint8

func ParseUint8(s string) (uint8, error)

func RegisterParser

func RegisterParser(f interface{}) error

RegisterParser takes a func (string) (<anytype>, error) and registers it on the default loader as the parser for <anytype>.

Types

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader is a helper for reading values from environment variables (or a map[string]string), converting them to Go types, and setting their values to fields on a user-provided struct.

func Empty

func Empty() *Loader

Empty returns a Loader without any parsers enabled.

func New

func New() (*Loader, error)

New returns a Loader with the default parsers enabled.

func (*Loader) Load

func (e *Loader) Load(c interface{}) error

Load loads config from the environment into the provided struct.

func (*Loader) LoadFromMap

func (e *Loader) LoadFromMap(vals map[string]string, c interface{}) error

LoadFromMap loads config from the provided map into the provided struct.

func (*Loader) MustRegisterParser added in v1.0.0

func (e *Loader) MustRegisterParser(f interface{})

MustRegisterParser attempts to register the provided parser func and panics if it gets an error.

func (*Loader) RegisterParser

func (e *Loader) RegisterParser(f interface{}) error

RegisterParser takes a func (string) (<anytype>, error) and registers it on the Loader as the parser for <anytype>

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL