Cost-effective way to have your app conform with 12 factor methodology with Go’s stock flag package.

Summary

Previously, before “cloud” was a thing, it was common to have configuration part of the source code, ie Rails’ config/database.yaml .

These days, with immutable infrastucture, separation of configuration and code is preferred; quoting 12 factor:

III. Config Store config in the environment An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes: - Resource handles to the database, Memcached, and other backing services - Credentials to external services such as Amazon S3 or Twitter - Per-deploy values such as the canonical hostname for the deploy Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.

– https://12factor.net/config

This means that the app’s context sets the configuration which enables the app to run transparently as a serverless function, in a kubernetes pod, in a cloud run, in a docker swarm, or your laptop.

Problem

replaced `viper` with `flag` package and🤯.



How do you justify adding a dependency if stdlib provides same functionality even if some plumbing required? #golang pic.twitter.com/4fAoXVP7vU — gmarik (@gmarik) August 27, 2019

Surprisingly often, in order to fulfill 12 Factor config requirements, people resort to packages with large API surface and as result large codebase and deep dependency graph.

Often times this is not necessary since the same functionality can be achieved with much less code and only using Go’s standard library packages. Here’s an example of using flag package to achieve equal result.

12 factor config with flag package

2 ways to configure the app, through: 1) cli flags or 2) environment variables

default values are configured from corresponding variables

environment variables, if configured, set the flag 's defaults using LookupOr* helpers

's defaults using helpers get full configuration with simple getConfig helper

package main import ( "flag" "fmt" "os" "strconv" "log" ) var ( // set by build process Git_Revision string Consul_URL string = "http://consul.local:8500" Statsd_URL string HTTP_ListenAddr string = ":8080" HTTP_Timeout int = 16 ) func main () { flag. StringVar (&Consul_URL, "consul-url" , LookupEnvOrString ( "CONSUL_URL" , Consul_URL), "service discovery url" ) flag. StringVar (&Statsd_URL, "statsd-url" , LookupEnvOrString ( "STATSD_URL" , Statsd_URL), "statsd's host:port" ) flag. StringVar (&HTTP_ListenAddr, "http-listen-addr" , LookupEnvOrString ( "HTTP_LISTEN_ADDR" , HTTP_ListenAddr), "http service listen address" ) flag. IntVar (&HTTP_Timeout, "http-timeout" , LookupEnvOrInt ( "HTTP_TIMEOUT" , HTTP_Timeout), "http timeout requesting http services" ) flag. Parse () log. Printf ( "app.config %v

" , getConfig (flag.CommandLine)) log. Println ( "app.status=starting" ) defer log. Println ( "app.status=shutdown" ) log. Println ( "hello world" ) } func LookupEnvOrString (key string , defaultVal string ) string { if val, ok := os. LookupEnv (key); ok { return val } return defaultVal } func LookupEnvOrInt (key string , defaultVal int ) int { if val, ok := os. LookupEnv (key); ok { v, err := strconv. Atoi (val) if err != nil { log. Fatalf ( "LookupEnvOrInt[%s]: %v" , key, err) } return v } return defaultVal } func getConfig (fs *flag.FlagSet) [] string { cfg := make ([] string , 0, 10) fs. VisitAll ( func (f *flag.Flag) { cfg = append (cfg, fmt. Sprintf ( "%s:%q" , f.Name, f.Value. String ())) }) return cfg }

see it in action on Playground

Conclusion

Pros

no dependencies other than standard library

Cons

a bit of plumbing code is required

defaults to environment var’s value if latter is set

env vars are manually named

description may duplicate var’s comments

flag package with combination with few helpers provides pragmatic way to configure your 12 factor-ready apps. It’s not perfect but gets the job done.

References