config

package module
v0.2.6 Latest Latest
Warning

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

Go to latest
Published: Nov 10, 2023 License: MIT Imports: 10 Imported by: 3

README

go-config

GoDoc Tests Go Report Card codecov

Effortless, stateful go configuration.

A straightforward go configuration library that supports flags, environment variables, toml, yaml and JSON configuration formats.

go-config also supports using multiple configuration format options at the same time. For example, you can provide flags and environment variables.

Because creating configuration files is tedious, go-config can generate configuration files for you. This helps remove human error from configuration files and makes configuration files easier to maintain since you only need to make changes in a single location.

All configuration options are controlled via struct tags. A 'struct' is the principal commanding player.

Getting Started

Get the library:

> go get github.com/hydronica/go-config

Use in your application:

package main

import (
    "os"
    
    "github.com/hydronica/go-config"
)

func main() {
    appCfg := options{}
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Username string
    Password string
}

Flags

Flag key defaults are lowercase separated by dashes.

// options example
type options struct {
    MyUsername string // Defaults to "my-username" as the flag key.
}

Flags follow the same default go-lang flag behavior. For example, there is no difference between the following two usages:

> ./myapp -my-username=username
> ./myapp --my-username=username

You may customize the flag name by providing the 'flag' struct tag.

type options struct {
    DBName string `flag:"db-name"`
}

You may embed structures.

type options struct {
    DBName  string `flag:"db-name"`
    DBCreds string `flag:"db"`
}

type DBCreds struct {
    Username string `flag:"un"`
    Password string `flag:"pw"`
}

You may provide flag aliases (for shorter referencing)

type options struct {
    DBName `flag:"db-name,n"` // "db-name" or "n" can be supplied.
    // Note: flag name conflicts (two or more flags with the same name) will cause config to return an error message 
    // and terminate the program.
}
> myapp -db.un=myusername -db-pw=mypassword

You may provide a description.

type options struct {
    DBName `flag:"db-name,n" comment:"It's the db name.` // "db-name" or "n" can be supplied.
    // Note: flag name conflicts (two or more flags with the same name) will cause config to return an error message 
    // and terminate the program.
}

You may ignore flag struct fields.

type options struct {
    DBName `flag:"-"` // DBName disabled for flags.
}

You may provide a default value. Regardless of the config input type a default is set by providing a struct value on initialization.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host     string `flag:"db-host,h" comment:"The db host:port."`
    Username string `flag:"db-un" comment:"The db username."`
    Password string `flag:"db-pw" comment:"The db password."`
}

You may ask for help.

> myapp -h # or --help
myapp

Available Flags:
-config,-c      The config file path (if using one). File extension must be one of "toml,yaml,yml,json,ini"
-gen,-g         Generate a config template file. Accepts one of "toml,yaml,yml,json,env", sends the template 
                to stdout and exits. Default values are pre-populated in a template. The 'env' template generates
                the environment values with a shebang for execution in a shell script file.
-show           Will show all config values and exit the application.

-db-host,-h     The db host:port. (default: localhost:5432)
-db-name,-n     It's the db name.
-db-pw
-db-un

You may disable flags entirely. Note, general config flags such as the '-gen' flag are not turned off and will still be shown on the help screen.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    config.DisableFlags()
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host    string
    DB      DB
}

type DB struct {
    Username string 
}

Environment Variables

The default naming of environment variables is to make the field name uppercase separated by underscores.

type options struct {
    Host    string             // Defaults to 'HOST'.
    DB      DB      `env:"DB"` // Acts as a namespace for structs.
}

type DB struct {
    Username string `env:"UN"` 
    Password string `env:"PW"`
}

...
# Expected environment variables.
HOST=myhost;
DB_UN=myusername;
DB_PW=mypassword;

You can generate an env template.

type options struct {
    Host    string             // Defaults to 'HOST'.
    DB      DB      `env:"DB"` // Acts as a namespace for structs.
}

type DB struct {
    Username string `env:"UN"` 
    Password string `env:"PW"`
}

...

> ./myapp -gen=env

#!/usr/bin/env bash

export HOST=localhost:5432;
export DB_UN=;
export DB_PW=;

# alternatively write directly to bash file.
> ./myapp -gen=env > myconfig.sh

When assigning structs as field values you may ignore the value as a prefix by using the "omitprefix" env value. This special value only works on struct and struct pointer types.

type options struct {
    Host    string                     // Defaults to 'HOST'.
    DB      DB      `env:"omitprefix"` // No prefix is expected or generated.
}

type DB struct {
    Username string `env:"UN"` 
    Password string `env:"PW"`
}

...

> ./myapp -gen=env

#!/usr/bin/env bash

export HOST=localhost:5432;
export UN=; # no prefix
export PW=; # no prefix

Other General Options

You may customize the help screen.

package main

import (
    "log"
    "os"
    
    "github.com/hydronica/go-config"
)

var hlp = `
{{app}}

Welcome to my application. The purpose of this application is to connect to the database
and demonstrate the power of go-config.

Flag Options:
{{flags}}
`

func main() {
    appCfg := options{}
    config.HelpTemplate(hlp)
    config.AppName("myapp") // Could also include the app version here.
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host     string `flag:"db-host,h" comment:"The db host:port."`
    Username string `flag:"db-un" comment:"The db username."`
    Password string `flag:"db-pw" comment:"The db password."`
}

You can disable a struct field entirely by providing the 'ignore' value in the 'config' tag.

type options struct {
    DBName `config:"ignore"` // DBName disabled entirely.
}

For longer descriptions you may call the "VarComment" package function.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    config.VarComment("Host", "once upon a time there was a very long description....")
    config.VarComment("DB.Username", "a really long custom description for the username field...")
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host    string
    DB      DB
}

type DB struct {
    Username string 
}

If the specified variable field is not found the config will return an error.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    config.VarComment("DoesNotExist", "once upon a time there was a very long description....")
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host    string
    DB      DB
}

...

> ./myapp

"DoesNotExist" is a field that does not exist.

You may specify if a field is required. By default, fields are not required.

type options struct {
    Host    string `req:"true"`
    DB      DB     `req:"false"` // Allowed but not necessary since this is the default.
}

Struct tags must be formed according to golang best practices. If not, then the option will not be honored.

type options struct {
    // Bad
    DB      string     `req` // Must provide the tag name followed by a colon and the value in quotes (no spaces).
}

type options struct {
    // Good
    DB      string     `req:"true"` // Must provide the tag name followed by a colon and the value in quotes (no spaces).
}

You may disable any configuration type. This is great for simplifying the application user experience so the user is not bombarded with too many options.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    config.DisableFlags() // disables custom flags.
    
    // Disabling any of the file config formats will mean that file type is not accepted 
    // for the application and options to generate a template of that type are also disabled.
    //
    // Users may wish to disable one or more formats to simplify the user experience. In this way
    // users are not given too many configuration choices.
    config.DisableTOML()
    config.DisableYAML()
    config.DisableJSON()
    
    // You may disable all file configuration types at once to make the application only accept flags and env variables.
    // If all file config types are disabled then the default help screen and flags will no longer support the 'config'
    // option.
    config.DisableFiles()
    
    // You may disable env.
    config.DisableEnv()
    
    // You may also specify that 'only' one configuration type is active.
    config.OnlyFlags()
    config.OnlyEnv()
    config.OnlyTOML()
    config.OnlyJSON()
   
    err := config.Load(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Host    string
    DB      DB
}

You may choose to provide a Validate() hook for more complex validations and config related initialization. This is also convenient from the perspective of unifying where initialization/config related errors come from.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    
    err := config.LoadWithValidation(&appCfg)
    if err != nil {
        println("err: %v", err.Error())
        os.Exit(0)
    }
}

type options struct {
    Username string `req:"true"`
    Password string
}

// Validate implements the config validator interface.
//
// Validate is called after the config values are read in.
func (o *options) Validate() error {
    if o.Username == "" && o.Password == "" {
        return errors.New("Invalid username password combination."
    }
}

Use "LoadOrDie" to automatically print the error and exit the program. This can further simplify initialization but at the expense of losing a little control.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    
    // If there is an error then the config library will display the error and terminate the 
    // application.
    config.LoadOrDie(&appCfg)
    
    // alternatively...
    config.LoadWithValidationOrDie(&appCfg)
}

type options struct {
    Username string `req:"true"`
    Password string
}

// Validate implements the config validator interface.
//
// Validate is called after the config values are read in.
func (o *options) Validate() error {
    if o.Username == "" && o.Password == "" {
        return errors.New("Invalid username password combination."
    }
}

After reading in config values you can dump the values to stderr. By default, everything is shown but sensitive information may be omitted by setting the "show" tag to false.

func main() {
    appCfg := options{
        Host: "localhost:5432", // default host value
    }
    
    config.Load(&appCfg)
    config.ShowValues() // Values dumped to stderr.
}

type options struct {
    Host     string
    Username string `req:"true"`
    Password string `show:"false"` // default is "true"
}

...

> ./myapp -host=myhost:5432 -username=myusername -password=mypassword
Host:     "myhost:5432" (default: "localhost:5432")
Username: "myusername"
Password: [redacted]

You may show the values by providing the 'show' flag. If provided, the application will show all the config values and exit.

> ./myapp -show -host=myhost:5432 -username=myusername -password=mypassword

Host:     "myhost:5432" (default: "localhost:5432")
Username: "myusername"
Password: [redacted]

All types support time.Time and time.Duration marshaling and unmarshaling.

time.Time default expected format is time.RFC339. You can specify a custom format in the value of the 'fmt' struct tag. Formatting is the same as that supported in the time package. For readability and simplicity you can also supply time package variable name of the format. For example, if you wanted to use the time.RFC3339Nano format the 'fmt' tag/value would be fmt:"RFC3339Nano". Unmarshaling will expect that format and marshaling will place the default value in that format. Marshaling will also automatically place an inline comment specifying the expected time format.

type options struct {
    DefaultFormat time.Time // Defaults to expect time.RFC339 format.
    OtherStandardFormat time.Time `fmt:"RFC339Nano"` // Expects the time.RFC339Nano format.
    CustomTimeField time.Time `fmt:"2006/01/02"`
}

func main() {
    cfg := &options{
        CustomTimeField: time.
    }
}

# env example but same idea for other formats.
> ./myapp -gen=env

#!/usr/bin/env bash
export DEFAULT_FORMAT= ; # "2006-01-02T15:04:05Z07:00" (RFC3339)
export OTHER_STANDARD_FORMAT= ; # "2006-01-02T15:04:05.999999999Z07:00" (RFC3339Nano)
export CUSTOM_TIME_FORMAT= ; # "2006/01/02"
Precedence

When a field value is provided through more than one avenue at once then the following takes precedence.

  1. Flags
  2. Config file (value from one of the config files)
  3. Environment
  4. Default value

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Load

func Load(c interface{}) error

func LoadEnv

func LoadEnv(c interface{}) error

LoadEnv is similar to LoadFile, but only checks env vars.

func LoadFile

func LoadFile(f string, c interface{}) error

LoadFile loads configuration values from a file (yaml, toml, json) into the struct configuration c.

This would be used if we only want to parse a file and don't want to use any other features. This is more or less what multi-config does.

func LoadFlag

func LoadFlag(c interface{}) error

LoadFlag is similar to LoadFile, but only checks flags.

func LoadOrDie

func LoadOrDie(c interface{})

func New

func New(c interface{}) *goConfig

New verifies that c is a valid - must be a struct pointer (maybe validation should happen in parse) it goes through the struct and sets up corresponding flags to be used during parsing

Types

type Validator

type Validator interface {
	Validate() error
}

Validator can be used as a way to validate the state of a config after it has been loaded.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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