106 lines
3.8 KiB
Markdown
106 lines
3.8 KiB
Markdown
# Viper Env Binding and Flag Binding
|
|
|
|
## The binding interaction model
|
|
|
|
Three settings control how viper maps environment variables to keys. They must be set together:
|
|
|
|
```go
|
|
viper.SetEnvPrefix("MYAPP") // adds MYAPP_ prefix
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // database.host → MYAPP_DATABASE_HOST
|
|
viper.AutomaticEnv() // activates auto-binding
|
|
```
|
|
|
|
Call these before any `ReadInConfig` or `viper.Get*` call — typically in a root command's `PersistentPreRunE` or in `init()`.
|
|
|
|
## AutomaticEnv vs BindEnv
|
|
|
|
| Method | Behavior |
|
|
| --- | --- |
|
|
| `AutomaticEnv()` | Every key is automatically mapped to its env equivalent (with prefix and replacer applied) |
|
|
| `BindEnv(key, envVars...)` | Only the specified key is bound, to the specified env var name(s) |
|
|
|
|
Use `AutomaticEnv` for the common case. Use `BindEnv` when you need to bind to an env var with a name that doesn't follow your prefix/replacer convention (e.g., third-party env vars like `GOOGLE_APPLICATION_CREDENTIALS`).
|
|
|
|
```go
|
|
// Bind a specific non-prefixed env var
|
|
viper.BindEnv("google.credentials", "GOOGLE_APPLICATION_CREDENTIALS")
|
|
```
|
|
|
|
## SetEnvKeyReplacer in depth
|
|
|
|
Viper keys use `.` as separator for nested values. Env vars cannot contain dots. The replacer maps between them.
|
|
|
|
```go
|
|
// Config file:
|
|
// database:
|
|
// host: localhost
|
|
// max_conn: 25
|
|
|
|
// Without replacer:
|
|
viper.SetEnvPrefix("MYAPP")
|
|
viper.AutomaticEnv()
|
|
viper.GetString("database.host") // looks for MYAPP_DATABASE.HOST — no match
|
|
|
|
// With replacer:
|
|
viper.SetEnvPrefix("MYAPP")
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
viper.AutomaticEnv()
|
|
viper.GetString("database.host") // looks for MYAPP_DATABASE_HOST — matches
|
|
```
|
|
|
|
The replacer operates on the viper key **before** prepending the prefix, so the lookup chain is: `database.host` → replace `.` with `_` → `database_host` → prepend prefix → `MYAPP_DATABASE_HOST`.
|
|
|
|
## AllowEmptyEnv
|
|
|
|
By default, viper ignores env vars set to the empty string — the empty string is treated as "not set" and viper continues down the precedence stack. Override this behavior:
|
|
|
|
```go
|
|
viper.AllowEmptyEnv(true)
|
|
// now MYAPP_PORT="" → viper.GetInt("port") == 0, not the default
|
|
```
|
|
|
|
## Flag binding
|
|
|
|
Bind a pflag after defining it:
|
|
|
|
```go
|
|
func init() {
|
|
rootCmd.PersistentFlags().Int("port", 8080, "listen port")
|
|
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
|
|
}
|
|
```
|
|
|
|
Bind an entire flag set:
|
|
|
|
```go
|
|
viper.BindPFlags(rootCmd.PersistentFlags())
|
|
```
|
|
|
|
**Timing rule:** Bind flags in `init()` or in `PersistentPreRunE`. The binding call must happen before `Execute()` parses flags — specifically, before any `viper.Get*` call on a flag-backed key. Binding after `Execute()` causes the flag's `Changed` state to be unknown, so viper may not promote the flag value to the correct precedence layer.
|
|
|
|
## How pflag binding interacts with precedence
|
|
|
|
Viper checks `flag.Changed` (whether the user explicitly passed the flag). This is how it distinguishes between "flag default" (low priority) and "flag explicitly set" (high priority):
|
|
|
|
- `flag.Changed == false` (flag has its default): viper treats the flag as not present and falls through to env/file/default.
|
|
- `flag.Changed == true` (flag was provided on the command line): viper treats the flag value as the highest-priority source.
|
|
|
|
This means `viper.GetInt("port")` correctly returns the flag value when `--port 9090` is passed, and falls back to env `MYAPP_PORT` or config file `port: 8080` otherwise.
|
|
|
|
## Debugging binding
|
|
|
|
Print all resolved values to verify your binding is correct:
|
|
|
|
```go
|
|
fmt.Println(viper.AllSettings())
|
|
// map[database:map[host:localhost max_conn:25] port:8080]
|
|
```
|
|
|
|
Check env var resolution:
|
|
|
|
```go
|
|
os.Setenv("MYAPP_PORT", "9090")
|
|
viper.AutomaticEnv()
|
|
fmt.Println(viper.GetInt("port")) // 9090
|
|
```
|