config: address edge case where local config specifies validation mode only (#16923)

This check ensures that local ScrapeConfigs that only specify Legacy validation do not inherit the default global AllowUTF8 escaping setting, which is an invalid combination of settings.

---------

Signed-off-by: Owen Williams <owen.williams@grafana.com>
This commit is contained in:
Owen Williams 2025-08-04 13:53:50 -04:00 committed by GitHub
parent 5cc49720ac
commit 74610b7c89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 16 deletions

View File

@ -3,6 +3,7 @@
## main / unreleased
* [BUGFIX] OTLP receiver: Generate `target_info` samples between the earliest and latest samples per resource. #16737
* [BUGFIX] Config: Infer escaping scheme when scrape config validation scheme is set.
## 3.5.0 / 2025-07-14

View File

@ -885,8 +885,10 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
return fmt.Errorf("unknown global name validation method specified, must be either '', 'legacy' or 'utf8', got %s", globalConfig.MetricNameValidationScheme)
}
// Scrapeconfig validation scheme matches global if left blank.
localValidationUnset := false
switch c.MetricNameValidationScheme {
case model.UnsetValidation:
localValidationUnset = true
c.MetricNameValidationScheme = globalConfig.MetricNameValidationScheme
case model.LegacyValidation, model.UTF8Validation:
default:
@ -906,8 +908,20 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
return fmt.Errorf("unknown global name escaping method specified, must be one of '%s', '%s', '%s', or '%s', got %q", model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues, globalConfig.MetricNameEscapingScheme)
}
// Similarly, if ScrapeConfig escaping scheme is blank, infer it from the
// ScrapeConfig validation scheme if that was set, or the Global validation
// scheme if the ScrapeConfig validation scheme was also not set. This ensures
// that local ScrapeConfigs that only specify Legacy validation do not inherit
// the global AllowUTF8 escaping setting, which is an error.
if c.MetricNameEscapingScheme == "" {
//nolint:gocritic
if localValidationUnset {
c.MetricNameEscapingScheme = globalConfig.MetricNameEscapingScheme
} else if c.MetricNameValidationScheme == model.LegacyValidation {
c.MetricNameEscapingScheme = model.EscapeUnderscores
} else {
c.MetricNameEscapingScheme = model.AllowUTF8
}
}
switch c.MetricNameEscapingScheme {

View File

@ -2804,26 +2804,37 @@ func TestScrapeConfigNameValidationSettings(t *testing.T) {
name string
inputFile string
expectScheme model.ValidationScheme
expectEscaping model.EscapingScheme
}{
{
name: "blank config implies default",
inputFile: "scrape_config_default_validation_mode",
expectScheme: model.UTF8Validation,
expectEscaping: model.NoEscaping,
},
{
name: "global setting implies local settings",
inputFile: "scrape_config_global_validation_mode",
expectScheme: model.LegacyValidation,
expectEscaping: model.DotsEscaping,
},
{
name: "local setting",
inputFile: "scrape_config_local_validation_mode",
expectScheme: model.LegacyValidation,
expectEscaping: model.ValueEncodingEscaping,
},
{
name: "local setting overrides global setting",
inputFile: "scrape_config_local_global_validation_mode",
expectScheme: model.UTF8Validation,
expectEscaping: model.DotsEscaping,
},
{
name: "local validation implies underscores escaping",
inputFile: "scrape_config_local_infer_escaping",
expectScheme: model.LegacyValidation,
expectEscaping: model.UnderscoreEscaping,
},
}
@ -2839,6 +2850,10 @@ func TestScrapeConfigNameValidationSettings(t *testing.T) {
require.NoError(t, yaml.UnmarshalStrict(out, got))
require.Equal(t, tc.expectScheme, got.ScrapeConfigs[0].MetricNameValidationScheme)
escaping, err := model.ToEscapingScheme(got.ScrapeConfigs[0].MetricNameEscapingScheme)
require.NoError(t, err)
require.Equal(t, tc.expectEscaping, escaping)
})
}
}

View File

@ -0,0 +1,3 @@
scrape_configs:
- job_name: prometheus
metric_name_validation_scheme: legacy