chore: Use single source of truth for config#483
chore: Use single source of truth for config#483ArthurSens wants to merge 3 commits intoprometheus-community:masterfrom
Conversation
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
kgeckhart
left a comment
There was a problem hiding this comment.
It's on a good path, further structural suggestion + more testing for drift (follow up candidate).
| DescriptorGoogleOnly bool | ||
| } | ||
|
|
||
| func SharedOptions() SharedConfig { |
There was a problem hiding this comment.
This looks unused, was it missed in a cleanup?
| return out | ||
| } | ||
|
|
||
| func DefaultRuntimeConfig() RuntimeConfig { |
|
|
||
| projectIDs = kingpin.Flag( | ||
| "google.project-ids", "Repeatable flag of Google Project IDs", | ||
| config.CLIFlag("ProjectIDs"), "Repeatable flag of Google Project IDs", |
There was a problem hiding this comment.
I'm not a huge fan of the magic strings here because they lead to runtime panics vs compile time errors. WDYT about adjusting the config structuring to something like,
type Option struct {
CLIFlag string
OTelKey string
Default any
}
var (
ProjectIDs = Option{CLIFlag: "google.project-ids", OTelKey: "project_ids"}
UniverseDomain = Option{CLIFlag: "google.universe-domain", OTelKey: "universe_domain", Default: DefaultUniverseDomain}
// ...
)Then this becomes config.ProjectIDs.CLIFlag and we can use it in the otelcollector side defaultComponentDefaults() config.<>.OTelKey / config.<>.Default
There was a problem hiding this comment.
Could we add a test to check for config drift with defaultConfig() and defaultComponentDefaults() since it's manually mapped? Something maybe like
func TestDefaultComponentDefaultsMatchDefaultConfig(t *testing.T) {
cfg := reflect.ValueOf(defaultConfig()).Elem()
typ := cfg.Type()
defaults := defaultComponentDefaults()
for i := 0; i < cfg.NumField(); i++ {
field := cfg.Field(i)
tag := typ.Field(i).Tag.Get("mapstructure")
if tag == "" || field.IsZero() {
continue // skip fields with no default
}
val, ok := defaults[tag]
require.True(t, ok, "key %q missing from defaultComponentDefaults", tag)
assert.Equal(t, field.Interface(), val, "mismatch for key %q", tag)
}
} There are other areas where config can drift in one side or between both sides (AI generated). Could be worth another follow up,
| # | Drift | How to Resolve |
|---|---|---|
| 1 | defaultConfig() ↔ defaultComponentDefaults() |
Reflect-based test: iterate non-zero fields of defaultConfig(), assert each appears in defaultComponentDefaults() with matching value. Catches new fields automatically. |
| 2 | otelcollector/Config fields ↔ RuntimeConfig fields |
Reflect-based test comparing the set of Option.OTelKey values against the mapstructure tags on Config and the field names on RuntimeConfig. Requires named Option vars + an AllOptions []Option slice to iterate. |
| 3 | runtimeConfig() doesn't map a field |
Round-trip test: populate every field of otelcollector/Config with a distinctive non-zero value, call runtimeConfig(), assert each RuntimeConfig field got the expected value. |
| 4 | collectorRuntimeConfigFromFlags() doesn't wire a field |
Same round-trip concept. Function reads package-level kingpin vars so can't be called with controlled values directly. Since stackdriver_exporter_test.go is already package main, tests can set package-level flag vars before calling the function — not elegant but avoids a structural change. Combined with a reflect check on the result it catches missing fields. |
| 5 | Flag variable defined but not included in collectorRuntimeConfigFromFlags() |
No clean automatic detection. Structural fix: register and read flags inline inside collectorRuntimeConfigFromFlags() using a helper, keeping declaration and use co-located. |
| 6 | mapstructure tags ↔ Option.OTelKey |
With named Option vars + AllOptions []Option: reflect-based test that for each Option, finds the otelcollector/Config field whose mapstructure tag matches Option.OTelKey. Fails if tag is wrong or field is missing. |
| 7 | runtimeConfig() and collectorRuntimeConfigFromFlags() handle a field differently |
Table-driven test that constructs equivalent inputs for both paths and asserts the resulting RuntimeConfig values are identical. CLI side uses the package-level var approach from 4. |
| 8 | Option var added without a corresponding otelcollector/Config field |
Covered by 6 — the same AllOptions iteration catches missing fields in either direction. |
| DescriptorGoogleOnly bool | ||
| } | ||
|
|
||
| func SharedOptions() SharedConfig { |
There was a problem hiding this comment.
I don't think this is used, left over from a refactor?
| return out | ||
| } | ||
|
|
||
| func DefaultRuntimeConfig() RuntimeConfig { |
After #477, configuration is duplicated between the original Stackdriver module and the new Otel-Collector module and it's easy to drift if we introduce new CLI flags or new config options to the collector YAML struct.
This PR is an attempt to re-introduce a single source of truth to both configs.