feat: add server validate subcommand and unknown key warnings#6307
feat: add server validate subcommand and unknown key warnings#6307denysvitali-kaiko wants to merge 1 commit intorunatlantis:mainfrom
server validate subcommand and unknown key warnings#6307Conversation
Atlantis silently ignores unknown keys in server config files via viper.Unmarshal(), which can lead to misconfiguration from typos (e.g. "allow-draft-pr" vs "allow-draft-prs") or invalid formats (e.g. "parallel_apply" vs "parallel-apply"). This adds: - `atlantis server validate --config <file>` subcommand that checks for unknown keys and exits non-zero if any are found - Warning on `atlantis server` startup when unknown keys are detected (non-breaking, doesn't block startup) - Helper functions buildKnownKeys() and findUnknownKeys() that derive valid keys from server.UserConfig mapstructure tags Signed-off-by: Denys Vitali <denys.vitali@kaiko.ai>
There was a problem hiding this comment.
Pull request overview
Adds a server validate CLI workflow and runtime warnings to detect unknown/typo’d keys in Atlantis server config files (which are otherwise silently ignored by Viper), improving config correctness and deploy safety.
Changes:
- Add
atlantis server validate --config <file>subcommand to fail on unknown config keys. - Warn on
atlantis serverstartup when unknown keys are present (non-blocking). - Add reflective helpers (
buildKnownKeys,findUnknownKeys) plus unit tests around validation and warning behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
main.go |
Wires the new server validate subcommand into the CLI command tree. |
cmd/server.go |
Adds known/unknown key detection helpers and logs unknown-key warnings during server startup. |
cmd/server_validate.go |
Implements the server validate subcommand that reads config and errors on unknown keys. |
cmd/server_validate_test.go |
Adds tests for validate behavior and for unknown-key detection/warnings. |
You can also share your feedback on Copilot code review. Take the survey.
| // findUnknownKeys returns config keys present in viper that do not correspond | ||
| // to any known Atlantis server flag. The result is sorted alphabetically. | ||
| func findUnknownKeys(v *viper.Viper) []string { | ||
| known := buildKnownKeys() | ||
| var unknown []string | ||
| for _, key := range v.AllKeys() { | ||
| if _, ok := known[key]; !ok { | ||
| unknown = append(unknown, key) | ||
| } | ||
| } |
There was a problem hiding this comment.
findUnknownKeys() iterates over viper.AllKeys(), which includes flattened nested keys (e.g. a valid webhooks: config will typically produce keys like webhooks.0.event, webhooks.0.kind, etc.). Since buildKnownKeys() only includes top-level UserConfig tags (like webhooks), valid nested config currently gets reported as unknown. Consider either validating only top-level keys (e.g. by iterating over viper.AllSettings() map keys or de-duping by the first segment before ".") or extending known-key generation to include nested key paths for structured fields like WebhookConfig.
| // findUnknownKeys returns config keys present in viper that do not correspond | |
| // to any known Atlantis server flag. The result is sorted alphabetically. | |
| func findUnknownKeys(v *viper.Viper) []string { | |
| known := buildKnownKeys() | |
| var unknown []string | |
| for _, key := range v.AllKeys() { | |
| if _, ok := known[key]; !ok { | |
| unknown = append(unknown, key) | |
| } | |
| } | |
| // findUnknownKeys returns top-level config keys present in viper that do not | |
| // correspond to any known Atlantis server flag. The result is sorted | |
| // alphabetically. | |
| func findUnknownKeys(v *viper.Viper) []string { | |
| known := buildKnownKeys() | |
| unknownSet := make(map[string]struct{}) | |
| for _, key := range v.AllKeys() { | |
| // viper.AllKeys() returns flattened keys (e.g. "webhooks.0.event"). | |
| // We only want to validate the top-level key (e.g. "webhooks"). | |
| topKey := key | |
| if parts := strings.SplitN(key, ".", 2); len(parts) > 0 { | |
| topKey = parts[0] | |
| } | |
| if _, ok := known[topKey]; !ok { | |
| unknownSet[topKey] = struct{}{} | |
| } | |
| } | |
| unknown := make([]string, 0, len(unknownSet)) | |
| for key := range unknownSet { | |
| unknown = append(unknown, key) | |
| } |
| Equals(t, "allow-draft-pr", unknowns[0]) | ||
| Equals(t, "tofu-version", unknowns[1]) | ||
| } | ||
|
|
There was a problem hiding this comment.
The unknown-key detection tests only cover flat keys. Since server config supports nested structures like webhooks (slice of objects), add a test case with a valid webhooks: config and assert it does not produce unknown keys; this would prevent regressions where viper's flattened nested keys are mistakenly treated as unknown.
| func TestFindUnknownKeys_WithWebhooksNestedConfig(t *testing.T) { | |
| t.Log("Should not report unknown keys for valid nested webhooks config.") | |
| v := viper.New() | |
| v.Set("repo-allowlist", "github.com/test/*") | |
| v.Set("webhooks", []map[string]any{ | |
| { | |
| "event": "apply", | |
| "workspace-regex": ".*", | |
| "kind": "slack", | |
| "secret": "secret", | |
| "url": "https://example.com/hook", | |
| }, | |
| }) | |
| unknowns := findUnknownKeys(v) | |
| Equals(t, 0, len(unknowns)) | |
| } |
server validate subcommand and unknown key warnings
What changed?
Added a
server validatesubcommand and unknown-key warnings on startup.Problem
Atlantis silently ignores unknown keys in server config files because
viper.Unmarshal()discards unrecognized keys by default. This meanstypos and invalid formats go unnoticed:
allow-draft-prinstead ofallow-draft-prsparallel_applyinstead ofparallel-applytofu-version(nonexistent key)There's currently no built-in way to validate a config file.
Solution
atlantis server validate --config <file>— reads the config filevia viper (same as
preRun()), checks for unknown keys, and exitsnon-zero if any are found. Does NOT require VCS credentials or start the
server.
Warning on
atlantis serverstartup — logs unknown keys at WARNlevel before proceeding. Non-breaking, doesn't block startup.
Helper functions
buildKnownKeys()andfindUnknownKeys()thatderive valid keys from
server.UserConfigmapstructure tagsreflectively, so they stay in sync automatically when new flags are
added.
Example
Why?
We discovered that our production Atlantis config had 4-5 silently ignored
keys (typos and wrong formats) that had been there for months. This
feature makes it easy to catch these in CI before deployment.
Changes
cmd/server.gobuildKnownKeys(),findUnknownKeys(), warning inrun()cmd/server_validate.gocmd/server_validate_test.gomain.goServerValidateCmdintoservercommandHow Has This Been Tested?
go test ./cmd/— all existing tests pass, 9 new tests added