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 ## main / unreleased
* [BUGFIX] OTLP receiver: Generate `target_info` samples between the earliest and latest samples per resource. #16737 * [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 ## 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) 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. // Scrapeconfig validation scheme matches global if left blank.
localValidationUnset := false
switch c.MetricNameValidationScheme { switch c.MetricNameValidationScheme {
case model.UnsetValidation: case model.UnsetValidation:
localValidationUnset = true
c.MetricNameValidationScheme = globalConfig.MetricNameValidationScheme c.MetricNameValidationScheme = globalConfig.MetricNameValidationScheme
case model.LegacyValidation, model.UTF8Validation: case model.LegacyValidation, model.UTF8Validation:
default: 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) 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 == "" { if c.MetricNameEscapingScheme == "" {
c.MetricNameEscapingScheme = globalConfig.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 { switch c.MetricNameEscapingScheme {

View File

@ -2801,29 +2801,40 @@ func TestScrapeConfigDisableCompression(t *testing.T) {
func TestScrapeConfigNameValidationSettings(t *testing.T) { func TestScrapeConfigNameValidationSettings(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
inputFile string inputFile string
expectScheme model.ValidationScheme expectScheme model.ValidationScheme
expectEscaping model.EscapingScheme
}{ }{
{ {
name: "blank config implies default", name: "blank config implies default",
inputFile: "scrape_config_default_validation_mode", inputFile: "scrape_config_default_validation_mode",
expectScheme: model.UTF8Validation, expectScheme: model.UTF8Validation,
expectEscaping: model.NoEscaping,
}, },
{ {
name: "global setting implies local settings", name: "global setting implies local settings",
inputFile: "scrape_config_global_validation_mode", inputFile: "scrape_config_global_validation_mode",
expectScheme: model.LegacyValidation, expectScheme: model.LegacyValidation,
expectEscaping: model.DotsEscaping,
}, },
{ {
name: "local setting", name: "local setting",
inputFile: "scrape_config_local_validation_mode", inputFile: "scrape_config_local_validation_mode",
expectScheme: model.LegacyValidation, expectScheme: model.LegacyValidation,
expectEscaping: model.ValueEncodingEscaping,
}, },
{ {
name: "local setting overrides global setting", name: "local setting overrides global setting",
inputFile: "scrape_config_local_global_validation_mode", inputFile: "scrape_config_local_global_validation_mode",
expectScheme: model.UTF8Validation, 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.NoError(t, yaml.UnmarshalStrict(out, got))
require.Equal(t, tc.expectScheme, got.ScrapeConfigs[0].MetricNameValidationScheme) 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