Skip to content

chore: Use single source of truth for config#483

Open
ArthurSens wants to merge 3 commits intoprometheus-community:masterfrom
ArthurSens:unified-config
Open

chore: Use single source of truth for config#483
ArthurSens wants to merge 3 commits intoprometheus-community:masterfrom
ArthurSens:unified-config

Conversation

@ArthurSens
Copy link
Copy Markdown
Contributor

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.

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
@ArthurSens ArthurSens requested a review from kgeckhart April 3, 2026 11:54
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Copy link
Copy Markdown
Contributor

@kgeckhart kgeckhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's on a good path, further structural suggestion + more testing for drift (follow up candidate).

DescriptorGoogleOnly bool
}

func SharedOptions() SharedConfig {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unused, was it missed in a cleanup?

return out
}

func DefaultRuntimeConfig() RuntimeConfig {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this


projectIDs = kingpin.Flag(
"google.project-ids", "Repeatable flag of Google Project IDs",
config.CLIFlag("ProjectIDs"), "Repeatable flag of Google Project IDs",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

@kgeckhart kgeckhart Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is used, left over from a refactor?

return out
}

func DefaultRuntimeConfig() RuntimeConfig {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This as well

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.

2 participants