Library for setting values to structs' fields from env, flags, files or default tag

Overview

Go Report Card codecov Go GoDoc Mentioned in Awesome Go

Configuration

is a library for injecting values recursively into structs - a convenient way of setting up a configuration object. Available features:

  • setting default values for struct fields - NewDefaultProvider()
  • setting values from environment variables - NewEnvProvider()
  • setting values from command line flags - NewFlagProvider(&cfg)
  • setting values from files (JSON or YAML) - NewFileProvider("./testdata/input.yml")

Supported types:

  • string, *string, []string
  • bool, *bool, []bool
  • int, int8, int16, int32, int64 + slices of these types
  • *int, *int8, *int16, *int32, *int64
  • uint, uint8, uint16, uint32, uint64 + slices of these types
  • *uint, *uint8, *uint16, *uint32, *uint64
  • float32, float64 + slices of these types
  • *float32, *float64
  • time.Duration from strings like 12ms, 2s etc.
  • embedded structs and pointers to structs

Why?

  • your entire configuration can be defined in one model
  • all metadata is in your model (defined with tags)
  • easy to set/change a source of data for your configuration
  • easy to set a priority of sources to fetch data from (e.g., 1.flags, 2.env, 3.default or another order)
  • you can implement your custom provider
  • only 2 external dependencies
  • complies with 12-factor app

Quick start

Import path github.com/BoRuDar/configuration/v3

// define a configuration object
cfg := struct {
    Name     string `flag:"name"`
    LastName string `default:"defaultLastName"`
    Age      byte   `env:"AGE_ENV"`
    BoolPtr  *bool  `default:"false"`

    ObjPtr *struct {
        F32       float32       `default:"32"`
        StrPtr    *string       `default:"str_ptr_test"`
        HundredMS time.Duration `default:"100ms"`
    }

    Obj struct {
        IntPtr   *int16   `default:"123"`
        NameYML  int      `default:"24"`
        StrSlice []string `default:"one;two"`
        IntSlice []int64  `default:"3; 4"`
    }
}{}

fileProvider, err := NewFileProvider("./testdata/input.yml")
if err != nil {
    t.Fatalf("unexpected error: %v", err)
}

configurator, err := New(
    &cfg, // pointer to the object for configuration 
    NewFlagProvider(&cfg),  // 1. flag provider expects pointer to the object to initialize flags
    NewEnvProvider(),       // 2.
    fileProvider,           // 3.
    NewDefaultProvider(),   // 4.
    // providers are executed in order of the declaration from 1 to 4 
)
if err != nil {
    t.Fatalf("unexpected error: %v", err)
}

configurator.InitValues()

Providers

You can specify one or more providers. They will be executed in order of definition:

[]Provider{
    NewFlagProvider(&cfg), // 1
    NewEnvProvider(), // 2
    NewDefaultProvider(), // 3
} 

If provider set value successfully next ones will not be executed (if flag provider from the sample above found a value env and default providers are skipped). The value of first successfully executed provider will be set. If none of providers found value - an application will be terminated. This behavior can be changed with configurator.SetOnFailFn method.

Custom provider

You can define a custom provider which should satisfy next interface:

type Provider interface {
	Provide(field reflect.StructField, v reflect.Value, pathToField ...string) error
}

Default provider

Looks for default tag and set value from it:

    struct {
        // ...
        Name string `default:"defaultName"`
        // ...
    }

Env provider

Looks for env tag and tries to find an ENV variable with the name from the tag (AGE in this example):

    struct {
        // ...
        Age      byte   `env:"AGE"`
        // ...
    }

Name inside tag env:"<name>" must be unique for each field.

Flag provider

Looks for flag tag and tries to set value from the command line flag -first_name

    struct {
        // ...
        Name     string `flag:"first_name|default_value|Description"`
        // ...
    }

Name inside tag flag:"<name>" must be unique for each field. default_value and description sections are optional and can be omitted. NewFlagProvider(&cfg) expects a pointer to the same object for initialization.

Note: if program is executed with -help or -h flag you will see all available flags with description:

Flags: 
	-first_name		"Description (default: default_value)"

And program execution will be terminated.

File provider

Doesn't require any specific tags. JSON and YAML formats of files are supported.

    NewFileProvider("./testdata/input.yml")
Issues
  • Add a Validation provider

    Add a Validation provider

    This provider can be used with the validate tag to provide validation on a field using go-playground/validator. This allows for proper validation, as well as better error checking.

    The way this provider works is by wrapping all other providers and if they don't error, it runs the value through Validate.Var() and returns the result. Part of this PR requires that the defaultProvider never returns an error, as it's reasonable that a desired default would be a nil value of some sort. This also makes the flagProvider error if the retrieved value is the default value. The reason for this, is that if the flagProvider is used , all other providers are passed on, because the flagProvider never has an empty value.

    This PR would resolve #14.

    opened by chabad360 6
  • Do not

    Do not "log.Fatal" within the library

    Hey, @BoRuDar, that's a great library! I enjoy the lack of non-trivial third-party dependencies and the idea of having a flags: tag - that's really convenient

    I may have a suggestion - do you think that it would be better not to Fatal() on InitValues() from within the library, but return the error to the caller, so that they could decide what to do with it?

    I know that you can set failIfCannotSet to false, it's just it seems that terminating the program from within library is a bit too much, because if you had something deferred this won't get executed, and it's always good to have the choice. If it was, God forbid, panic, at least one could recover.

    I take that you already got my point, but being courteous to other people that might be reading this, I will add an example:

    func main() {
    	cfg := config.Config{}
    	parser, err := configuration.New(
    		&cfg,
    		[]configuration.Provider{
    			configuration.NewFlagProvider(&cfg),
    			configuration.NewEnvProvider(),
    		},
    		false,
    		true,
    	)
    	if err != nil {
    		log.Fatal(err)
    	}
    	// The following line FATALs away
    	parser.InitValues()
    	// main logic ...
    }
    

    Proposed change:

    func main() {
    	cfg := config.Config{}
    	parser, err := configuration.New(
    		&cfg,
    		[]configuration.Provider{
    			configuration.NewFlagProvider(&cfg),
    			configuration.NewEnvProvider(),
    		},
    		false,
    		true,
    	)
    	if err != nil {
    		log.Fatal(err)
    	}
    	// After InitValues modified to return the value rather than die with shame
    	if err := parser.InitValues(); err != nil {
    		log.Println("oh shit, your configuration is borked, and that's why: %s", err)
    		// close file descriptors
    		// unwind winded things
    		// terminate hostile elements
    		log.Fatal("gg")
    	}
    	// main logic ...
    }
    
    discussion 
    opened by rusq 5
  • Suggestion: Parsing tags on nested structures

    Suggestion: Parsing tags on nested structures

    Hi @BoRuDar , it's me again, your new friend.

    Noticed that tags (on flag provider) are not being parsed on nested structure, so, say, on this structure:

    type Config struct {
    	Client *Client `yaml:"client,omitempty" flag:"name|default_value|Description"`
    	Server *Server `yaml:"server,omitempty"`
    }
    
    type Client struct {
    	// ServerAddress is the default server address to connect to.
    	ServerAddress string `yaml:"default_server,omitempty" flag:"addr|127.0.0.1:443|server address"`
    }
    
    type Server struct {
    // ...
    }
    

    only the tag for Client will be parsed.

    In my particular case the requirement is to have a config file that can include server and client configuration values, because executable can act as a client or server.

    Would you agree that it would be very useful to transverse the nested structures too?

    bug resolved 
    opened by rusq 3
  • [#6] Addressing fatal

    [#6] Addressing fatal

    Hey Bogdan,

    This is just a sample PR for what I would do with those fatals (following the item no. 1 of proposed changes in Issue #6).

    Sorry, I could not resist and renamed Global logger to gLogger, to follow the pattern with global variables which other two variables set.

    I wouldn't want you to feel obliged to merge, however feel free too if you feel confident in the changes :)

    Will be happy to address any suggestions or have a discussion around proposed changes.

    Cheers, Rustam

    discussion on hold 
    opened by rusq 2
  • Ignore default value from `FlagProvider`

    Ignore default value from `FlagProvider`

    This PR will cause FlagProvider to ignore the flag if its value is the default value set as fd.defaultVal in favor of allowing the default tag to set it.

    This PR would resolve #14.

    opened by chabad360 1
  • Cant use an empty default for FlagProvider

    Cant use an empty default for FlagProvider

    It would be preferable if the FlagProvider respected the default tag, because otherwise it just introduces duplication, and if it's used before any other providers, all the other ones get ignored. The issue with not using a default is that its not possible to provide a description, because the second part of the tag is used for the default. If the default is left empty it's still used, meaning that all the other tags are still skipped.

    Basically, FlagProvider should check if the default tag is present, if it is, then FlagProvider should not set a default (but should describe it as such in the usage output).

    bug resolved 
    opened by chabad360 1
  • V3

    V3

    • Provider interface is changed
    • Function signatures are changed a bit
    • Logger is encapsulated
    • Fatal on error can be changed
    • Better error handling
    • Test coverage is improved
    enhancement 
    opened by BoRuDar 1
Releases(v4.0.0)
Owner
Bogdan Daragan
Software engineer, DevOps
Bogdan Daragan
A Go (golang) environment loader (which loads env vars from a .env file)

A Go (golang) environment loader (which loads env vars from a .env file)

Bendt Indonesia 0 Feb 8, 2022
A simple tool that utilizes already existing libraries such as joho/godotenv to add .env-files to global path

Go dotenv A simple tool that utilizes already existing libraries such as joho/godotenv to add .env-files to global path. Created as a practical way to

null 0 Nov 15, 2021
Tmpl - A tool to apply variables from cli, env, JSON/TOML/YAML files to templates

tmpl allows to apply variables from JSON/TOML/YAML files, environment variables or CLI arguments to template files using Golang text/template and functions from the Sprig project.

krako 1 May 30, 2022
goconfig uses a struct as input and populates the fields of this struct with parameters from command line, environment variables and configuration file.

goconfig goconfig uses a struct as input and populates the fields of this struct with parameters from command line, environment variables and configur

Go Sidekick 0 May 30, 2022
Genv is a library for Go (golang) that makes it easy to read and use environment variables in your projects. It also allows environment variables to be loaded from the .env file.

genv Genv is a library for Go (golang) that makes it easy to read and use environment variables in your projects. It also allows environment variables

Şakir Şensoy 28 Mar 5, 2022
A Go port of Ruby's dotenv library (Loads environment variables from `.env`.)

GoDotEnv A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) From the original Library: Storing configuration in the

John Barton 5k Jun 27, 2022
Light weight, extensible configuration management library for Go. Built in support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

koanf (pronounced conf; a play on the Japanese Koan) is a library for reading configuration from different sources in different formats in Go applicat

Kailash Nadh 1k Jun 26, 2022
Tis module used as base fo configuration apps.By default, it expands into the inside of the application.

Tis module used as base fo configuration apps.By default, it expands into the inside of the application. Also, module c reads a dictionary of secrets from the application directory by its AppName and extension json.

LordTor 0 Dec 7, 2021
persistent storage for flags in go

ingo is a simple Go library helping you to persist flags in a ini-like config file. Features and limitations Requires Go 1.5 or later automatically cr

null 36 Jun 22, 2022
Flags-first package for configuration

ff stands for flags-first, and provides an opinionated way to populate a flag.FlagSet with configuration data from the environment.

Peter Bourgon 1k Jun 27, 2022
formicidate is a small tool for Go application can update the value of environment variables in a .env file with code

formicidae Update .env files in Go with code. What is fomicidae? formicidate is a small tool for Go application. You can update the value of environme

akuma 0 Jan 23, 2022
Simple lib to parse environment variables to structs

env Simple lib to parse envs to structs in Go. Example A very basic example: package main import ( "fmt" "time" // if using go modules "github.c

Carlos Alexandro Becker 2.5k Jun 26, 2022
Un-marshaling environment variables to Go structs

envcfg Un-marshaling environment variables to Go structs Getting Started Let's set a bunch of environment variables and then run your go app #!/usr/bi

Tomaž Kovačič 98 May 4, 2022
Library providing routines to merge and validate JSON, YAML and/or TOML files

CONFLATE Library providing routines to merge and validate JSON, YAML, TOML files and/or structs (godoc) Typical use case: Make your application config

Andy 25 May 5, 2022
🛠 A configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP

config A small configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP. Example func main() {

Josh Betz 210 Apr 10, 2022
Golang library for reading properties from configuration files in JSON and YAML format or from environment variables.

go-config Golang library for reading properties from configuration files in JSON and YAML format or from environment variables. Usage Create config in

null 3 Feb 22, 2022
Tinyini - Bare-bones Go library for reading INI-like configuration files

tinyini tinyini is a minimalistic library for parsing INI-like configuration files. example configuration file globalkey = globalvalue [section] key

null 0 Jan 10, 2022
A golang package for parsing ini-style configuration files

Mini Mini is a simple ini configuration file parser. The ini syntax supported includes: The standard name=value Comments on new lines starting with #

Stephen Asbury 30 Apr 2, 2022
Manage local application configuration files using templates and data from etcd or consul

confd confd is a lightweight configuration management tool focused on: keeping local configuration files up-to-date using data stored in etcd, consul,

Kelsey Hightower 7.8k Jun 25, 2022