config

package module
v0.1.3 Latest Latest
Warning

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

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

README

go-conf - Configuration with less code

Go Reference build Go Report Card Quality Gate Status Coverage Code Smells Security Rating Maintainability Rating Reliability Rating Vulnerabilities CodeQL

Installation

go get github.com/ThomasObenaus/go-conf

What is go-conf?

go-conf is a solution for handling configurations in golang applications.

go-conf supports reading configuration parameters from multiple sources. The order they are applied is:

  1. Default values are overwritten by
  2. Parameters defined in the config-file, which are overwritten by
  3. Environment variables, which are overwritten by
  4. Command-Line parameters

The aim is to write as less code as possible:

  • No need to write code to integrate multiple libraries that support reading a configuration from file/ commandline or the environment.
  • No need to code to take the values from that library to fill it into the config struct you want to use in your app anyway.

Instead one just has to define the config structure and annotates it with struct tags.

package main

import (
    "fmt"

    config "github.com/ThomasObenaus/go-conf"
)

// Define the config struct and annotate it with the cfg tag.
type MyFontConfig struct {
    Color string `cfg:"{'name':'color','desc':'The value of the color as hexadecimal RGB string.','default':'#FFFFFF'}"`
    Name  string `cfg:"{'name':'name','desc':'Name of the font to be used.'}"`
    Size  int    `cfg:"{'name':'size','desc':'Size of the font.','short':'s'}"`
}

func main() {

    // Some command line arguments
    args := []string{
        // color not set  --> default value will be used "--color=#ff00ff",
        "--name=Arial",
        "-s=12", // use -s (short hand version) instead of --size
    }

    // 1. Create an instance of the config struct that should be filled
    cfg := MyFontConfig{}

    // 2. Create an instance of the config provider
    provider, err := config.NewConfigProvider(&cfg, "MY_APP", "MY_APP")
    if err != nil {
        panic(err)
    }

    // 3. Read the config and populate the struct
    if err := provider.ReadConfig(args); err != nil {
        panic(err)
    }

    // 4. Thats it! Now the config can be used.
    fmt.Printf("FontConfig: color=%s, name=%s, size=%d\n", cfg.Color, cfg.Name, cfg.Size)
}

Features
  • Automatically populates a struct using values given via command line
  • Read config parameters from multiple sources like command line, environment variables and config files (yaml)
  • Support of default values
  • Short hand parameters for command line flags
  • Print usage on command line
  • Custom mapping functions to support parsing of config parameters into complex structures and type conversion
  • Support of config parameter lists
  • Support of complex structs with multiple levels

License

FOSSA Status

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewConfigProvider

func NewConfigProvider(target interface{}, configName, envPrefix string, options ...ProviderOption) (interfaces.Provider, error)

NewConfigProvider creates a new config provider that is able to parse the command line, env vars and config file based on the given entries. This config provider automatically generates the needed config entries and fills the given config target based on the annotations on this struct. In case custom config entries should be used beside the annotations on the struct one can define them via

CustomConfigEntries(customEntries)`

e.g.

customEntries:=[]Entry{
// fill entries here
}
provider,err := NewConfigProvider(&myConfig,"my-config","MY_APP",CustomConfigEntries(customEntries))
Example (MappingFunc)
type logLevel int8
const (
	llTrace logLevel = 0
	llDebug logLevel = 1
	llInfo  logLevel = 2
	llWarn  logLevel = 3
	llError logLevel = 4
)

// The configuration with the annotations needed in order to define how the config should be filled
type myCfg struct {
	Field1 string   `cfg:"{'name':'field-1','mapfun':'strToUpper','default':'HeLlO wOrLd'}"`
	Field2 logLevel `cfg:"{'name':'field-2','mapfun':'strToLogLevel'}"`
}
cfg := myCfg{}

// Create a provider based on the given config struct
provider, err := NewConfigProvider(&cfg, "MyConfig", "MY_APP")
if err != nil {
	panic(err)
}

// Register function to convert each string to upper case
err = provider.RegisterMappingFunc("strToUpper", func(rawUntypedValue interface{}, targetType reflect.Type) (interface{}, error) {
	asStr, ok := rawUntypedValue.(string)
	if !ok {
		return nil, fmt.Errorf("Value must be a string")
	}
	return strings.ToUpper(asStr), nil
})
if err != nil {
	panic(err)
}

// Register function to convert the given loglevel as string to the actual loglevel
err = provider.RegisterMappingFunc("strToLogLevel", func(rawUntypedValue interface{}, targetType reflect.Type) (interface{}, error) {
	asStr, ok := rawUntypedValue.(string)
	if !ok {
		return nil, fmt.Errorf("Value must be a string")
	}

	switch asStr {
	case "trace":
		return llTrace, nil
	case "debug":
		return llDebug, nil
	case "info":
		return llInfo, nil
	case "warn":
		return llWarn, nil
	case "error":
		return llError, nil
	default:
		return nil, fmt.Errorf("loglevel %s unknown/ not supported", asStr)
	}
})
if err != nil {
	panic(err)
}

// As commandline arguments the parameter 'field-1' is missing, hence its default value will be used (see above)
args := []string{
	"--field-2=warn",
}

// Read the parameters given via commandline into the config struct
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

fmt.Printf("field-1='%v', field-2='%v'\n", cfg.Field1, cfg.Field2)
Output:

field-1='HELLO WORLD', field-2='3'
Example (PrimitiveTypes)
// The configuration with the annotations needed in order to define how the config should be filled
type myCfg struct {
	Field1 string        `cfg:"{'name':'field-1','desc':'This is field 1','default':'default value for field 1'}"`
	Field2 int           `cfg:"{'name':'field-2','desc':'This is field 2','default':11}"`
	Field3 bool          `cfg:"{'name':'field-3','desc':'This is field 3','default':false}"`
	Field4 time.Duration `cfg:"{'name':'field-4','desc':'This is field 4','default':'5m'}"`
}
cfg := myCfg{}

// Create a provider based on the given config struct
provider, err := NewConfigProvider(&cfg, "MyConfig", "MY_APP")
if err != nil {
	panic(err)
}

// As commandline arguments the parameter 'field-1' is missing, hence its default value will be used (see above)
args := []string{"--field-2=22", "--field-3", "--field-4=10m"}

// Read the parameters given via commandline into the config struct
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

fmt.Printf("field-1='%s', field-2=%d, field-3=%t, field-4=%s\n", cfg.Field1, cfg.Field2, cfg.Field3, cfg.Field4)
Output:

field-1='default value for field 1', field-2=22, field-3=true, field-4=10m0s
Example (Slices)
// The configuration with the annotations needed in order to define how the config should be filled
type myCfg struct {
	Field1 []string  `cfg:"{'name':'field-1','desc':'This is field 1','default':['a','b','c']}"`
	Field2 []int     `cfg:"{'name':'field-2','desc':'This is field 2','default':[1,2,3]}"`
	Field3 []bool    `cfg:"{'name':'field-3','desc':'This is field 3','default':[true,false,true]}"`
	Field4 []float64 `cfg:"{'name':'field-4','desc':'This is field 4','default':[1.1,2.2,3.3]}"`
}
cfg := myCfg{}

// Create a provider based on the given config struct
provider, err := NewConfigProvider(&cfg, "MyConfig", "MY_APP")
if err != nil {
	panic(err)
}

// As commandline arguments the parameter 'field-1' is missing, hence its default value will be used (see above)
args := []string{
	"--field-2=4,5,6",
	"--field-3=false,true",
	"--field-4=4.4,5.5,6.6",
}

// Read the parameters given via commandline into the config struct
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

fmt.Printf("field-1='%v', field-2=%v, field-3=%v\n", cfg.Field1, cfg.Field2, cfg.Field3)
Output:

field-1='[a b c]', field-2=[4 5 6], field-3=[false true]
Example (Structs)
type myNestedStruct struct {
	FieldA string `cfg:"{'name':'field-a','desc':'This is field a','default':'default of field a'}"`
	FieldB int    `cfg:"{'name':'field-b','desc':'This is field b','default':22}"`
}
// The configuration with the annotations needed in order to define how the config should be filled
type myCfg struct {
	Field1 myNestedStruct   `cfg:"{'name':'field-1','desc':'This is field 1','default':{'field-a':'default','field-b':33}}"`
	Field2 []myNestedStruct `cfg:"{'name':'field-2','desc':'This is field 2','default':[{'field-a':'value','field-b':33},{}]}"`
}
cfg := myCfg{}

// Create a provider based on the given config struct
provider, err := NewConfigProvider(&cfg, "MyConfig", "MY_APP")
if err != nil {
	panic(err)
}

// As commandline arguments the parameter 'field-1' is missing, hence its default value will be used (see above)
args := []string{
	"--field-1.field-a=the value",
}

// Read the parameters given via commandline into the config struct
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

fmt.Printf("field-1='%v', field-2=%v\n", cfg.Field1, cfg.Field2)
Output:

field-1='{the value 22}', field-2=[{value 33} {default of field a 22}]
Example (Usage)
// The configuration with the annotations needed in order to define how the config should be filled
type myCfg struct {
	//Field1 string `cfg:"{'name':'field-1','desc':'This is field 1','default':'default value for field 1'}"`
	Field2 int `cfg:"{'name':'field-2','desc':'This is field 2. It is a required field since no default values is defined.'}"`
}
cfg := myCfg{}

// Create a provider based on the given config struct
provider, err := NewConfigProvider(&cfg, "MyConfig", "MY_APP")
if err != nil {
	panic(err)
}

args := []string{"--field-2=22"}

// Read the parameters given via commandline into the config struct
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

fmt.Print(provider.Usage())
Output:

--field-2 (-) [required]
	env var: MY_APP_FIELD_2
	default: n/a
	desc: This is field 2. It is a required field since no default values is defined.

func NewProvider

func NewProvider(configEntries []Entry, configName, envPrefix string, options ...ProviderOption) (interfaces.Provider, error)

NewProvider creates a new config provider that is able to parse the command line, env vars and config file based on the given entries.

DEPRECATED: Please use NewConfigProvider instead.

Example
var configEntries []Entry

configEntries = append(configEntries, NewEntry("port", "the port to listen to", Default(8080), ShortName("p")))
// no default value for this parameter --> thus it can be treated as a required one
// to check if it was set by the user one can just call provider.IsSet("db-url")
configEntries = append(configEntries, NewEntry("db-url", "the address of the data base"))
configEntries = append(configEntries, NewEntry("db-reconnect", "enable automatic reconnect to the data base", Default(false)))

provider, err := NewProvider(configEntries, "my-config", "MY_APP")
if err != nil {
	panic(err)
}
args := []string{"--db-url=http://localhost"}

err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

port := provider.GetInt("port")
// check for mandatory parameter
if !provider.IsSet("db-url") {
	panic(fmt.Errorf("Parameter '--db-url' is missing"))
}
dbURL := provider.GetString("db-url")
dbReconnect := provider.GetBool("db-reconnect")

fmt.Printf("port=%d, dbURL=%s, dbReconnect=%t", port, dbURL, dbReconnect)
Output:

port=8080, dbURL=http://localhost, dbReconnect=false
Example (WithConfigFile)
var configEntries []Entry

configEntries = append(configEntries, NewEntry("port", "the port to listen to", Default(8080), ShortName("p")))

provider, err := NewProvider(configEntries, "my-config", "MY_APP")
if err != nil {
	panic(err)
}

args := []string{"--config-file=test/data/config.yaml"}
err = provider.ReadConfig(args)
if err != nil {
	panic(err)
}

port := provider.GetInt("port")
cfgFile := provider.GetString("config-file")

fmt.Printf("port=%d was read from cfgFile=%s", port, cfgFile)
Output:

port=12345 was read from cfgFile=test/data/config.yaml

Types

type Entry

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

Entry is one item to define a configuration

func CreateEntriesFromStruct

func CreateEntriesFromStruct(target interface{}, logger interfaces.LoggerFunc) ([]Entry, error)

CreateEntriesFromStruct creates Entries based on the annotations provided at the given target struct.

Only fields with annotations of the form

`cfg:"{'name':<name>,'desc':<description>,'default':<default value>}"`

will be regarded.

For example for the struct below

type Cfg struct {
	Name string `cfg:"{'name':'name','desc':'the name of the config','default':'the name'}"`
}

A config entry

e := NewEntry("name","the name of the config",Default("the name"))

will be created.

func NewEntry

func NewEntry(name, usage string, options ...EntryOption) Entry

NewEntry creates a new Entry that is available as flag, config file entry and environment variable

Example
entry := NewEntry("port", "The port of the service", Default(8080), ShortName("p"))
fmt.Printf("%s", entry)
Output:

--port (-p) [default:8080 (int)]	- The port of the service

func (Entry) EnvVarName added in v0.1.0

func (e Entry) EnvVarName(envPrefix string) string

EnvVarName returns the name of the environment variable for this entry.

func (Entry) IsRequired

func (e Entry) IsRequired() bool

IsRequired returns true in case no default value is given

func (Entry) Name

func (e Entry) Name() string

Name provides the specified name for this entry

func (Entry) String

func (e Entry) String() string

type EntryOption

type EntryOption func(e *Entry)

EntryOption represents an option for the Entry

func Bind

func Bind(flag, env bool) EntryOption

Bind enables/ disables binding of flag and env var

func Default

func Default(value interface{}) EntryOption

Default specifies a default value

func DesiredType

func DesiredType(t reflect.Type) EntryOption

DesiredType sets the desired type of this entry

func ShortName

func ShortName(fShort string) EntryOption

ShortName specifies the shorthand (one-letter) flag name

type ProviderOption

type ProviderOption func(p *providerImpl)

ProviderOption represents an option for the Provider

func CfgFile

func CfgFile(parameterName, shortParameterName string) ProviderOption

CfgFile specifies a default value

func CustomConfigEntries

func CustomConfigEntries(customConfigEntries []Entry) ProviderOption

CustomConfigEntries allows to add config entries that are created manually via NewEntry(..)

func Logger

func Logger(logger interfaces.LoggerFunc) ProviderOption

Logger exchanges the logger function. This provides the possibility to integrate your own logger. Per default the NoLogging function is used (disables logging completely). Other predefined logger functions (based on fmt.Printf) are DebugLogger, InfoLogger, WarnLogger and ErrorLogger.

Directories

Path Synopsis
test
mocks
Package mock_interfaces is a generated GoMock package.
Package mock_interfaces is a generated GoMock package.

Jump to

Keyboard shortcuts

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