feat(config): add -config/-profile config file and profile overlays - #357
Conversation
extend the existing goflags yaml config mechanism (the same one -template already uses) with an explicit -config path and named -profile overlays, instead of adding a second config system. resolveConfigInput unifies -config/-profile/-template into a single flat yaml path for goflags to merge before Parse: unset, it returns "" and goflags falls back to its ambient ~/.config/sif/config.yaml unchanged. a profile overlays its keys onto the file's top-level keys in a go map before handing goflags a flat temp file, so precedence stays explicit cli flag > profile > file default > built-in default via goflags' own DefValue sentinel merge. -config and -template share one config slot and are mutually exclusive. verifies empirically that goflags auto-creates and merges the ambient config file with no explicit SetConfigFilePath call, which this feature depends on.
template-example.toml was unreferenced by any go file and did not match how goflags actually reads config (flat long-name keys, yaml). replace it with config-example.yaml showing the real schema, including a profiles block, and document -config/-profile in the usage/configuration guides and the man page.
resolveConfigInput used to return an explicit -config path unparsed when -profile was not set, so a malformed yaml file skipped validation entirely. goflags then silently discarded the decode error, dropping every real setting in the file with no diagnostic and exit 0, while the same file with -profile already errored cleanly through loadConfigMap. route both branches through one buildFlatConfig that always loads via loadConfigMap and only overlays a profile when one is selected, so a malformed file errors the same way regardless of -profile.
goflags' own merge (readConfigFile) treats a flag whose current value equals its DefValue as "unset" and applies the config value over it. that makes an explicit cli flag silently lose to the config file or a profile whenever the user happens to pass the flag's own default, e.g. "-timeout 10s" against the built-in 10s default: today the file's 1s wins even though the flag was set explicitly on the command line. the same class of bug hits -threads, -concurrency, -notify-severity, and any profile value on the same merge path. scan the raw args for every flag the user actually passed (long or short alias, space or "=" form) and strip those keys out of the resolved config/ profile map before it ever reaches goflags, so cli precedence holds unconditionally instead of only when the value differs from the default. flagAliasGroups derives the long/short alias groups from the real flag registration (grouping by the shared flag.Value pointer) rather than a second hardcoded name table, so it can't drift from registerFlags. goflags also swallows a type-mismatched config value entirely (discards fl.Value.Set's error); that is a separate, lower-severity gap in the vendored dependency itself and is left alone, noted in a comment.
pr summary9 files changed (+668 -34)
|
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #357 +/- ##
=======================================
Coverage ? 55.08%
=======================================
Files ? 82
Lines ? 6984
Branches ? 0
=======================================
Hits ? 3847
Misses ? 2857
Partials ? 280 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
vmfunc
left a comment
There was a problem hiding this comment.
solid. the load-bearing bit is the explicit-key strip in buildFlatConfig: goflags treating flag==DefValue as unset would silently let the file win over an explicit -timeout 10s, and pulling those keys out of the map before it ever reaches goflags is the right fix. good that it's pinned by TestExplicitFlagAtDefaultBeatsFile and the short-alias variant, that's exactly the case that'd rot quietly otherwise.
flagAliasGroups deriving the alias table from registerFlags instead of a second hardcoded name map is the right call, it can't drift.
and routing the no-profile path through loadConfigMap so a malformed -config errors loudly instead of goflags swallowing the decode and running with every setting dropped, that's a real bug you closed, not just tidiness. TestMalformedConfigNoProfileErrors covers it.
one sharp edge, non-blocking: explicitFlagTokens counts any -x arg as a flag token, so a string-flag value that's literally another flag name (-cookie -t, cookie value "-t") would strip timeout out of the config and revert it to the built-in default. contrived enough that i wouldn't hold the PR for it, and fixing it properly means reintroducing the arity table you deliberately dodged. leave it, maybe a comment noting the limitation.
ship it.
adds a -config flag to load flag defaults from a yaml file, and a -profile flag to select a named overlay within it, so a recurring scan setup doesn't need to be retyped as flags every run. explicit cli flags still win over anything in the config/profile - a flag's DefValue is compared against what the user actually passed so an unset flag can be overridden by the file without a set flag being silently clobbered. includes malformed-config validation on the no-profile path and a documented example config.