Skip to content

Repository files navigation

Go Environment Helper

Go Tests Coverage Code Coverage Lint

This package provides a simple way to load environment variables from a variety of files - with default value support - and an atomic API to read and write environment variables. Files supported:

  • .env
  • .ini
  • .yaml

Usage

Global application environment

The package uses a global environment variable for the general application environment; default ENV. This can be overridden with the variable of your choosing, e.g.: env.GlobalEnv = "APP_ENV".

The global environment can be set by calling SetEnv. Common environment shorthands are automatically parsed. The main environment types are:

  • development
  • production
  • testing
  • staging

The package comes with some helper functions to check the global environment, e.g. IsDevelopment(), IsStaging() et cetera.

Feel free to use a custom environment type, fitting your application's needs.

Note

The default environment is development, unless ENV (<= env.GlobalVar) is set on machine level. E.g.: docker run -e ENV=production my-application

API & Manual injection

The package comes with a simple atomic API to get and set environment variables. It supports all stringable data types such as strings, integers, booleans, floats, and slices.

The API features two types of getters:

  • Get<T>(key, default_value)
    This is the safest way to handle non-mandatory environment variables, using sensible default values
  • Must<T>(key)
    This is the quick short-hand way to force environment variables. This will throw a system level panic if the variable is unset or unparsable.

You can inject variables using the Set() function, which can come in handy when you need specific validation / variables inside your libraries.

See Basic example for a short example on how to work with the API.

DotEnv

Import: github.com/vpmv/go-env/dotenv

This library supports loading environment variables from .env files, using joho/godotenv as the file processor, making it easier to overload (custom) files into your application environment.

Files overload each other in the following order:

  • .env
  • .env.local
  • .env.<app_env>
  • .env.<app_env>.local
  • <custom_file>

INI

Import: github.com/vpmv/go-env/ini

This library supports loading environment variables from .ini files, using gopkg.in/ini.v1 as the file processor.

Files overload each other in the following order:

  • env.ini
  • env.local.ini
  • env.<app_env>.ini
  • env.<app_env>.local.ini
  • <custom_file>

You can also map your (overloaded) files directly to a struct using Map(), or access the *ini.File object using LoadFile().

YAML

Import: github.com/vpmv/go-env/yaml

This library supports loading environment variables from .yaml files, using goccy/go-yaml as the file processor, with the support of YAML anchors.

Files overload each other in the following order:

  • env.yaml
  • env.local.yaml
  • env.<app_env>.yaml
  • env.<app_env>.local.yaml
  • <custom_file>

You can also map your (overloaded) files directly to a struct using Map(), or MapWithReferences().

Foreign Anchors

Note

The package goccy/go-yaml supports loading anchors from other files. Although it is designed to support reference directories, we explicitly only support reference files, because reference directories will evaluate all files consecutively prior to parsing. If an unknown reference is found, it'll stop execution.

If you want to use YAML anchors defined in different files, you can supply paths to these reference files. This allows you to easily reuse/overwrite blocks of configuration.

Related functions are:

  • yaml.LoadWithReferences - loading contents into the environment
  • yaml.MapWithReferences - mapping contents to an interface

All files are expected to be relative to the basedir.

Examples

Basic example

package main

import (
    "github.com/vpmv/go-env"	
    "github.com/vpmv/go-env/dotenv"	
)

func main() {
    dotenv.Load(`/config/`)
    
    if env.IsDevelopment() {
        env.Set(`SEED_DB`, true)
    }
    
    database := env.MustString(`DATABASE_URL`) // will panic if unset
    databasePort := env.GetInt(`DATABASE_PORT`, 3306) // will return default value (3306) if unset
    // ...
	
	// check if variable exists
	if env.Has(`DATABASE_MIGRATE`) {
		// ...
    }
}

Working with slices

Slice values are separated with a semi-colon (;) by default. You can override this behaviour using: env.SetDelimiter(",").

Note

Setting the delimiter will affect all subsequent operations. You must explicitly set it before parsing data or reading files, and (re)set it according to the desired output.

package main

import (
	"github.com/vpmv/go-env"
)

func main() {
	env.Set(`ALLOWED_ORIGINS`, []string{`10.0.0.0/8`, `192.168.0.0/16`})
	//  ALLOWED_ORIGINS=10.0.0.0/8;192.168.0.0/16
	
	// specify a custom delimiter for slice-types
	// NOTE: the delimiter remains in memory until changed
	env.SetDelimiter(`,`)
	env.Set(`NUMBERS`, []int{101,202,303})
	//  NUMBERS=101,202,303
	
	// without resetting the delimiter, you'll get a single-value slice
	origins := env.GetStringSlice(`ALLOWED_ORIGINS`, []string{`*`})
	// []string{`10.0.0.0/8;192.168.0.0/16`} 
	
	
	env.SetDelimiter(`;`) // reset to default delimiter 
	origins := env.GetStringSlice(`ALLOWED_ORIGINS`, []string{`*`})
	// []string{`10.0.0.0/8`, `192.168.0.0/16`}
}

INI files

Parse INI to environment

package main

import (
	"fmt"

	"github.com/go-fuego/fuego"
	"github.com/vpmv/go-env"
	"github.com/vpmv/go-env/ini"
)

func main() {
	env.SetEnv(true, `app`) // set custom ENV
	
	ini.Load(`/config/`)
	host := env.GetString(`APP_HOST`, `localhost`)
	port := env.GetInt(`APP_PORT`, 8080)

	server := fuego.NewServer(
		fuego.WithAddr(fmt.Sprintf("%s:%d", host, port))
	)
	fuego.Use(server, cors.New(cors.Options{
		AllowedOrigins: env.GetStringSlice(`APP_ALLOWED_ORIGINS`, []string{`*`}),
	}))
}

Map environment INI to struct

package main

import (
	"github.com/vpmv/go-env"
	"github.com/vpmv/go-env/ini"
)

type Config struct {
    App struct {
        Host string `ini:"host"`
        Port int    `ini:"port"`
    } `ini:"app"`
    Meta struct {
        JWTSecret string `ini:"jwt"`
        TTL       int    `ini:"ttl"`
    } `ini:"app.meta"`
    Database struct {
        Host     string `ini:"host"`
        Port     int    `ini:"port"`
        User     string `ini:"user"`
        Password string `ini:"password"`
        Seed     bool   `ini:"bool"`
    } `ini:"database"`
}

func main() {
	env.SetEnv(true, `emergency`) // set custom ENV
	
	config := new(Config)
	_ = ini.Map(config, `/config/`)
}

YAML files

Parse YAML to environment

Because YAML supports arrays, environment variables are set with a logical iterator. For example env.yaml parses to:

  • LIST[0] => cheese;cake;gherkin
  • LIST[1] => bread;wine;prayer
  • LISTMAP[0]_HOST => mysql
  • LISTMAP[0]_PORT => 3306
  • LISTMAP[1]_HOS => postgres
  • LISTMAP[1]_PORT => 5432
package main

import (
	"fmt"

	"github.com/go-fuego/fuego"
	"github.com/vpmv/go-env"
	"github.com/vpmv/go-env/yaml"
)

func main() {
	err := yaml.Load(`/config`)
	if err != nil {
		// ...
    }
	host := env.GetString(`APP_HOST`, `localhost`)
	port := env.GetInt(`APP_PORT`, 8080)
	
	mysql := storage.NewClient(
		env.MustString(`DATABASE[0]_HOST`),
		env.MustInt(`DATABASE[0]_PORT`),
    )
	redis := storage.NewClient(
		env.MustString(`DATABASE[1]_HOST`),
		env.MustInt(`DATABASE[1]_PORT`),
    )
	
	server := fuego.NewServer(
		fuego.WithAddr(fmt.Sprintf("%s:%d", host, port))
	)
	fuego.Use(server, cors.New(cors.Options{
		AllowedOrigins: env.GetStringSlice(`APP_ALLOWED_ORIGINS`, []string{`*`}),
	}))
}

Map environment YAML to struct

package main

import (
	"github.com/vpmv/go-env"
	"github.com/vpmv/go-env/yaml"
)

type Config struct {
    App struct {
        Host     string   `yaml:"host"`
        Port     int      `yaml:"port"`
        Origins  []string `yaml:"allowed_origins"`
	} `yaml:"app"`
    Database    struct {
        Host  string  `yaml:"host"`
        Port  int     `yaml:"port"`
        User  string  `yaml:"username"`
        Pass  string  `yaml:"password"`
    } `yaml:"database"`
}

func main() {
	config := new(Config)
	err := yaml.MapWithReferences(config, `/app/config`, []string{`defaults.yaml`})
}

About

Atomic environment helper for INI, DotEnv and YAML files - with default value support

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages