Skip to content

Make the whole configuration settable from the environment - #7

Open
jmeekhof wants to merge 1 commit into
jaxxstorm:mainfrom
jmeekhof:fix/env-dns-provider
Open

Make the whole configuration settable from the environment#7
jmeekhof wants to merge 1 commit into
jaxxstorm:mainfrom
jmeekhof:fix/env-dns-provider

Conversation

@jmeekhof

@jmeekhof jmeekhof commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

The bug

dns.provider has no environment binding. cmd.go binds TAILSCALE_API_KEY, TAILSCALE_TAILNET, DNS_ZONE_ID, DNS_DOMAIN, CLOUDFLARE_API_TOKEN, AWS_PROFILE, AWS_REGION and the PIHOLE_* pair — but never dns.provider.

viper.AutomaticEnv() doesn't cover the gap, because no key replacer is configured: viper looks for a variable literally named DNS.PROVIDER, so DNS_PROVIDER never maps to the nested key.

Since Config.Validate() hard-requires dns.provider, a config file is mandatory even when every other setting comes from the environment. For a container deployment that otherwise needs no file, that means bind-mounting a file containing a single line.

The README's own "Environment Variables" section shows the symptom — it has to pass the provider as a flag alongside the exported variables:

export TAILSCALE_API_KEY="tskey-api-xxxxx"
...
./dnsscale --dns-provider cloudflare   # <- the one setting that can't come from the env

The same gap silently affects app.workers, app.poll_interval, app.required_tags, logging.level and logging.format, none of which were bound either.

What changed

  • Register a strings.NewReplacer(".", "_") key replacer, so nested keys are addressable as DNS_PROVIDER, LOGGING_LEVEL and so on.
  • Bind every configuration key explicitly. AutomaticEnv only resolves keys viper already knows about, which makes the set of settings that actually work from the environment hard to predict; an explicit table means the mapping is the same whether or not a config file was loaded, and it's greppable.
  • AWS_PROFILE and AWS_REGION are kept rather than moving to the DNS_ROUTE53_* names the replacer would otherwise imply — those are the conventional names and the AWS SDK reads them anyway.
  • Document the full key-to-variable mapping in the README, and drop the --dns-provider flag from the env-only examples.

No behaviour changes for existing config files or flags; precedence is unchanged (an explicitly-set flag still wins over the environment).

Tests

Adds cmd_test.go, which unmarshals and validates a configuration built purely from environment variables with no file present. Against the current binding set that test fails with exactly the reported symptom:

cmd_test.go:38: dns.provider = "", want route53
cmd_test.go:70: Validate() on env-only config: dns.provider is required

go build ./..., go vet ./... and go test ./... all pass.

dns.provider had no environment binding. viper.AutomaticEnv() was in
play but no key replacer was configured, so it looked for a variable
literally named "DNS.PROVIDER" and DNS_PROVIDER never mapped to the
nested key. Because Config.Validate() hard-requires dns.provider, a
configuration file was mandatory even when every other setting was
supplied through the environment - which is awkward for container
deployments that otherwise need no file at all.

The README's own "Environment Variables" example showed this: it had to
pass --dns-provider as a flag alongside the exported variables.

- Register a strings.NewReplacer(".", "_") key replacer so nested keys
  are addressable as DNS_PROVIDER, LOGGING_LEVEL and so on.
- Bind every configuration key explicitly, including the app.* and
  logging.* settings which were also unreachable. AutomaticEnv only
  resolves keys viper already knows about, so relying on it alone makes
  the set of settings that work from the environment hard to predict.
- The AWS settings keep AWS_PROFILE and AWS_REGION rather than moving to
  the DNS_ROUTE53_* names the replacer would otherwise imply.
- Document the full key-to-variable mapping in the README.

Adds a test that unmarshals and validates a configuration built purely
from environment variables, with no file present.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant