An opinionated take on handling Go configs: put your secrets in SSM, put the rest in ENV vars and this package will load them all. And you can also load from JSON because... why not?
- Minimal API, maximal power: One function (
Load()), 3Loaderand 6 ways to load the data (ENV vars, SSM var holding a JSON, a local JSON file, a[]byteslice, anio.Readeror (preferred overio.Reader) anio.ReadSeeker); - Minimal dependencies: Only SSM loader pulls in AWS SDK v2, stdlib for everything else;
- Composability: Layer environment variables, SSM, and JSON loaders in any order—later loaders override earlier ones;
- Robust ENV var support: The env variable names are inferred from the struct field name
and the passed prefix (if non empty) and can also be overriden on a per-field basis using the
struct tag
env(e.g.env:"MYAPP_FOO"). Field names in CamelCase are converted to UPPER_SNAKE_CASE for environment variable lookup. Acronyms are handled so thatAWSRegionbecomesAWS_REGION, andMyIDbecomesMY_ID. - Robust type support: When loading from env it handles primitives, slices, nested structs, booleans (with many/common string forms such as t/f, yes/no, etc.) and time durations out of the box;
- Testable by example: Code coverage is achieved with concise, real-world examples that double as documentation;
- Bring Your Own Loader: If builtin loaders don't fit your needs, you
can easily implement your own loader by implementing the(not at the moment, TBD);Loaderinterface - Unknown field/var detection: Optionally error if unknown fields/vars are present in the data but not in the target config, but ONLY AFTER the data has been loaded, so you can still use the config and just warn about the unknown fields.
| Loader | Source Type | Example Usage |
|---|---|---|
| WithErrOnUnknown | N/A | This sets the option to err on unknown fields/vars |
| WithEnv | ENV prefix (string) | WithEnv("MYAPP") |
| WithSSM | SSM key (string) | WithSSM("/my/key", "us-east-1") |
| WithJSON | file path (string) | WithJSON("config.json") |
| WithJSON | []byte | WithJSON([]byte(jsonData)) |
| WithJSON | io.ReadSeeker | WithJSON(bytes.NewReader(data)) |
| WithJSON | io.Reader | WithJSON(os.Stdin) |
import "github.com/alexaandru/confetti"
var cfg MyConfig
func init() {
if err := confetti.Load(&cfg, confetti.WithEnv("MYAPP")); err != nil {
panic(err)
}
}cfg := MyConfig{}
err := confetti.Load(&cfg, confetti.WithErrOnUnknown(), confetti.WithJSON("./config.json"))
if errors.Is(err, confetti.ErrUnknownFields) {
// handle unknown fields error
}No direct support for default values however, you can provide the cfg pre-populated
and you can also Make the zero value useful
which together should cover most use cases.
Another option would be to use go:embed to embed a JSON file with the defaults, while using other loaders to override it, i.e.
//go:embed defaults.json
var defaultConfig []byte
func init() {
if err := confetti.Load(&cfg,
confetti.WithJSON(defaultConfig),
confetti.WithJSON(".env.production.json")); err != nil {
panic(err)
}
}For more examples see: ENV, JSON, ENV+JSON and SSM Loader examples.