diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go
index ed7aa52c8..db05ce7ed 100644
--- a/cmd/prometheus/main.go
+++ b/cmd/prometheus/main.go
@@ -141,10 +141,6 @@ var (
)
func init() {
- // This can be removed when the legacy global mode is fully deprecated.
- //nolint:staticcheck
- model.NameValidationScheme = model.UTF8Validation
-
prometheus.MustRegister(versioncollector.NewCollector(strings.ReplaceAll(appName, "-", "_")))
var err error
diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go
index e4262f1b3..ed549090b 100644
--- a/cmd/prometheus/main_test.go
+++ b/cmd/prometheus/main_test.go
@@ -46,12 +46,6 @@ import (
"github.com/prometheus/prometheus/util/testutil"
)
-func init() {
- // This can be removed when the legacy global mode is fully deprecated.
- //nolint:staticcheck
- model.NameValidationScheme = model.UTF8Validation
-}
-
const startupTime = 10 * time.Second
var (
diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go
index c6a5801d2..18703fe5b 100644
--- a/cmd/promtool/main.go
+++ b/cmd/promtool/main.go
@@ -63,12 +63,6 @@ import (
var promqlEnableDelayedNameRemoval = false
-func init() {
- // This can be removed when the legacy global mode is fully deprecated.
- //nolint:staticcheck
- model.NameValidationScheme = model.UTF8Validation
-}
-
const (
successExitCode = 0
failureExitCode = 1
@@ -161,6 +155,7 @@ func main() {
checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage)
checkMetricsExtended := checkCmd.Flag("extended", "Print extended information related to the cardinality of the metrics.").Bool()
+ checkMetricsScheme := checkCmd.Flag("validation-scheme", "Validate metric and label names").Default("utf8").Enum("legacy", "utf8")
agentMode := checkConfigCmd.Flag("agent", "Check config file for Prometheus in Agent mode.").Bool()
queryCmd := app.Command("query", "Run query against a Prometheus server.")
@@ -372,7 +367,17 @@ func main() {
os.Exit(CheckRules(newRulesLintConfig(*checkRulesLint, *checkRulesLintFatal, *checkRulesIgnoreUnknownFields), *ruleFiles...))
case checkMetricsCmd.FullCommand():
- os.Exit(CheckMetrics(*checkMetricsExtended))
+ validationScheme := model.UnsetValidation
+ switch *checkMetricsScheme {
+ case "legacy":
+ validationScheme = model.LegacyValidation
+ case "utf8":
+ validationScheme = model.UTF8Validation
+ default:
+ fmt.Fprintln(os.Stderr, "invalid validation scheme: "+*checkMetricsScheme)
+ os.Exit(1)
+ }
+ os.Exit(CheckMetrics(*checkMetricsExtended, validationScheme))
case pushMetricsCmd.FullCommand():
os.Exit(PushMetrics(remoteWriteURL, httpRoundTripper, *pushMetricsHeaders, *pushMetricsTimeout, *pushMetricsLabels, *metricFiles...))
@@ -1017,11 +1022,11 @@ $ curl -s http://localhost:9090/metrics | promtool check metrics
`)
// CheckMetrics performs a linting pass on input metrics.
-func CheckMetrics(extended bool) int {
+func CheckMetrics(extended bool, validationScheme model.ValidationScheme) int {
var buf bytes.Buffer
tee := io.TeeReader(os.Stdin, &buf)
l := promlint.New(tee)
- problems, err := l.Lint()
+ problems, err := l.Lint(validationScheme)
if err != nil {
fmt.Fprintln(os.Stderr, "error while linting:", err)
return failureExitCode
diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go
index f922d18c4..b2f38ce13 100644
--- a/cmd/promtool/main_test.go
+++ b/cmd/promtool/main_test.go
@@ -39,12 +39,6 @@ import (
"github.com/prometheus/prometheus/promql/promqltest"
)
-func init() {
- // This can be removed when the legacy global mode is fully deprecated.
- //nolint:staticcheck
- model.NameValidationScheme = model.UTF8Validation
-}
-
var promtoolPath = os.Args[0]
func TestMain(m *testing.M) {
diff --git a/config/config.go b/config/config.go
index 12ca828ae..9cb51f754 100644
--- a/config/config.go
+++ b/config/config.go
@@ -14,6 +14,7 @@
package config
import (
+ "cmp"
"errors"
"fmt"
"log/slog"
@@ -417,6 +418,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if rwcfg == nil {
return errors.New("empty or null remote write config section")
}
+ if err := rwcfg.Validate(c.GlobalConfig); err != nil {
+ return err
+ }
// Skip empty names, we fill their name with their config hash in remote write code.
if _, ok := rwNames[rwcfg.Name]; ok && rwcfg.Name != "" {
return fmt.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
@@ -434,6 +438,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
rrNames[rrcfg.Name] = struct{}{}
}
+ if err := c.AlertingConfig.Validate(c.GlobalConfig); err != nil {
+ return errors.New("invalid alerting config: " + err.Error())
+ }
return nil
}
@@ -595,15 +602,7 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
- if err := gc.ExternalLabels.Validate(func(l labels.Label) error {
- if !model.LabelName(l.Name).IsValid() {
- return fmt.Errorf("%q is not a valid label name", l.Name)
- }
- if !model.LabelValue(l.Value).IsValid() {
- return fmt.Errorf("%q is not a valid label value", l.Value)
- }
- return nil
- }); err != nil {
+ if err := gc.validateExternalLabels(); err != nil {
return err
}
@@ -637,6 +636,23 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
+// validationScheme if specified, utf-8 validation otherwise.
+func (c GlobalConfig) validationScheme() model.ValidationScheme {
+ return cmp.Or(c.MetricNameValidationScheme, model.UTF8Validation)
+}
+
+func (c GlobalConfig) validateExternalLabels() error {
+ return c.ExternalLabels.Validate(func(l labels.Label) error {
+ if !model.LabelName(l.Name).IsValid(c.validationScheme()) {
+ return fmt.Errorf("%q is not a valid label name", l.Name)
+ }
+ if !model.LabelValue(l.Value).IsValid() {
+ return fmt.Errorf("%q is not a valid label value", l.Value)
+ }
+ return nil
+ })
+}
+
// isZero returns true iff the global config is the zero value.
func (c *GlobalConfig) isZero() bool {
return c.ExternalLabels.IsEmpty() &&
@@ -871,11 +887,6 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
}
}
- //nolint:staticcheck
- if model.NameValidationScheme != model.UTF8Validation {
- return errors.New("model.NameValidationScheme must be set to UTF8")
- }
-
switch globalConfig.MetricNameValidationScheme {
case model.UnsetValidation:
globalConfig.MetricNameValidationScheme = model.UTF8Validation
@@ -889,7 +900,7 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
c.MetricNameValidationScheme = globalConfig.MetricNameValidationScheme
case model.LegacyValidation, model.UTF8Validation:
default:
- return fmt.Errorf("unknown scrape config name validation method specified, must be either '', 'legacy' or 'utf8', got %s", c.MetricNameValidationScheme)
+ return fmt.Errorf("unknown scrape config name validation method specified, must be either 'legacy' or 'utf8', got %v", c.MetricNameValidationScheme)
}
// Escaping scheme is based on the validation scheme if left blank.
@@ -929,6 +940,33 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
c.AlwaysScrapeClassicHistograms = &global
}
+ // Validate relabel configs
+ namingScheme := c.MetricNameValidationScheme
+
+ // Check for users putting URLs in target groups.
+ if len(c.RelabelConfigs) == 0 {
+ if err := checkStaticTargets(c.ServiceDiscoveryConfigs); err != nil {
+ return err
+ }
+ }
+
+ for _, rlcfg := range c.RelabelConfigs {
+ if rlcfg == nil {
+ return errors.New("empty or null target relabeling rule in scrape config")
+ }
+ if err := rlcfg.Validate(namingScheme); err != nil {
+ return errors.New("invalid relabel config: " + err.Error())
+ }
+ }
+ for _, rlcfg := range c.MetricRelabelConfigs {
+ if rlcfg == nil {
+ return errors.New("empty or null metric relabeling rule in scrape config")
+ }
+ if err := rlcfg.Validate(namingScheme); err != nil {
+ return errors.New("invalid metric relabel config: " + err.Error())
+ }
+ }
+
return nil
}
@@ -1097,11 +1135,25 @@ func (c *AlertingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
if err := unmarshal((*plain)(c)); err != nil {
return err
}
+ return nil
+}
+func (c *AlertingConfig) Validate(globalConfig GlobalConfig) error {
+ for _, amcfg := range c.AlertmanagerConfigs {
+ if amcfg == nil {
+ return errors.New("empty or null alert manager config")
+ }
+ if err := amcfg.Validate(globalConfig); err != nil {
+ return err
+ }
+ }
for _, rlcfg := range c.AlertRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null alert relabeling rule")
}
+ if err := rlcfg.Validate(globalConfig.MetricNameValidationScheme); err != nil {
+ return err
+ }
}
return nil
}
@@ -1174,6 +1226,10 @@ type AlertmanagerConfig struct {
RelabelConfigs []*relabel.Config `yaml:"relabel_configs,omitempty"`
// Relabel alerts before sending to the specific alertmanager.
AlertRelabelConfigs []*relabel.Config `yaml:"alert_relabel_configs,omitempty"`
+
+ // Allow UTF8 Metric and Label Names. Can be blank in config files but must
+ // have a value if a AlertmanagerConfig is created programmatically.
+ MetricNameValidationScheme model.ValidationScheme `yaml:"metric_name_validation_scheme,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
@@ -1188,7 +1244,10 @@ func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er
if err := discovery.UnmarshalYAMLWithInlineConfigs(c, unmarshal); err != nil {
return err
}
+ return nil
+}
+func (c *AlertmanagerConfig) Validate(globalConfig GlobalConfig) error {
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
// We cannot make it a pointer as the parser panics for inlined pointer structs.
// Thus we just do its validation here.
@@ -1210,16 +1269,27 @@ func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er
}
}
+ validationScheme := cmp.Or(
+ c.MetricNameValidationScheme,
+ globalConfig.validationScheme(),
+ )
+
for _, rlcfg := range c.RelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null Alertmanager target relabeling rule")
}
+ if err := rlcfg.Validate(validationScheme); err != nil {
+ return errors.New("invalid relabel config: " + err.Error())
+ }
}
for _, rlcfg := range c.AlertRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null Alertmanager alert relabeling rule")
}
+ if err := rlcfg.Validate(validationScheme); err != nil {
+ return errors.New("invalid alert relabel config: " + err.Error())
+ }
}
return nil
@@ -1322,6 +1392,10 @@ type RemoteWriteConfig struct {
SigV4Config *sigv4.SigV4Config `yaml:"sigv4,omitempty"`
AzureADConfig *azuread.AzureADConfig `yaml:"azuread,omitempty"`
GoogleIAMConfig *googleiam.Config `yaml:"google_iam,omitempty"`
+
+ // Allow UTF8 Metric and Label Names. Can be blank in config files but must
+ // have a value if a RemoteWriteConfig is created programmatically.
+ MetricNameValidationScheme model.ValidationScheme `yaml:"metric_name_validation_scheme,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
@@ -1336,13 +1410,25 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
if err := unmarshal((*plain)(c)); err != nil {
return err
}
+ return nil
+}
+
+func (c *RemoteWriteConfig) Validate(globalConfig GlobalConfig) error {
if c.URL == nil {
return errors.New("url for remote_write is empty")
}
+
+ namingScheme := cmp.Or(
+ c.MetricNameValidationScheme,
+ globalConfig.validationScheme(),
+ )
for _, rlcfg := range c.WriteRelabelConfigs {
if rlcfg == nil {
return errors.New("empty or null relabeling rule in remote write config")
}
+ if err := rlcfg.Validate(namingScheme); err != nil {
+ return errors.New("invalid relabel config: " + err.Error())
+ }
}
if err := validateHeaders(c.Headers); err != nil {
return err
@@ -1477,6 +1563,10 @@ type RemoteReadConfig struct {
// Whether to use the external labels as selectors for the remote read endpoint.
FilterExternalLabels bool `yaml:"filter_external_labels,omitempty"`
+
+ // Allow UTF8 Metric and Label Names. Can be blank in config files but must
+ // have a value if a RemoteReadConfig is created programmatically.
+ MetricNameValidationScheme model.ValidationScheme `yaml:"metric_name_validation_scheme,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
diff --git a/config/config_test.go b/config/config_test.go
index 0673748b9..10749f128 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -124,11 +124,12 @@ var expectedConf = &Config{
Name: "drop_expensive",
WriteRelabelConfigs: []*relabel.Config{
{
- SourceLabels: model.LabelNames{"__name__"},
- Separator: ";",
- Regex: relabel.MustNewRegexp("expensive.*"),
- Replacement: "$1",
- Action: relabel.Drop,
+ SourceLabels: model.LabelNames{"__name__"},
+ Separator: ";",
+ Regex: relabel.MustNewRegexp("expensive.*"),
+ Replacement: "$1",
+ Action: relabel.Drop,
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
QueueConfig: DefaultQueueConfig,
@@ -278,50 +279,56 @@ var expectedConf = &Config{
RelabelConfigs: []*relabel.Config{
{
- SourceLabels: model.LabelNames{"job", "__meta_dns_name"},
- TargetLabel: "job",
- Separator: ";",
- Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
- Replacement: "foo-${1}",
- Action: relabel.Replace,
+ SourceLabels: model.LabelNames{"job", "__meta_dns_name"},
+ TargetLabel: "job",
+ Separator: ";",
+ Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
+ Replacement: "foo-${1}",
+ Action: relabel.Replace,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- SourceLabels: model.LabelNames{"abc"},
- TargetLabel: "cde",
- Separator: ";",
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.Replace,
+ SourceLabels: model.LabelNames{"abc"},
+ TargetLabel: "cde",
+ Separator: ";",
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.Replace,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- TargetLabel: "abc",
- Separator: ";",
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: "static",
- Action: relabel.Replace,
+ TargetLabel: "abc",
+ Separator: ";",
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: "static",
+ Action: relabel.Replace,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- TargetLabel: "abc",
- Separator: ";",
- Regex: relabel.MustNewRegexp(""),
- Replacement: "static",
- Action: relabel.Replace,
+ TargetLabel: "abc",
+ Separator: ";",
+ Regex: relabel.MustNewRegexp(""),
+ Replacement: "static",
+ Action: relabel.Replace,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- SourceLabels: model.LabelNames{"foo"},
- TargetLabel: "abc",
- Action: relabel.KeepEqual,
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Separator: relabel.DefaultRelabelConfig.Separator,
+ SourceLabels: model.LabelNames{"foo"},
+ TargetLabel: "abc",
+ Action: relabel.KeepEqual,
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Separator: relabel.DefaultRelabelConfig.Separator,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- SourceLabels: model.LabelNames{"foo"},
- TargetLabel: "abc",
- Action: relabel.DropEqual,
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Separator: relabel.DefaultRelabelConfig.Separator,
+ SourceLabels: model.LabelNames{"foo"},
+ TargetLabel: "abc",
+ Action: relabel.DropEqual,
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Separator: relabel.DefaultRelabelConfig.Separator,
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
},
@@ -376,54 +383,61 @@ var expectedConf = &Config{
RelabelConfigs: []*relabel.Config{
{
- SourceLabels: model.LabelNames{"job"},
- Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.Drop,
+ SourceLabels: model.LabelNames{"job"},
+ Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.Drop,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- SourceLabels: model.LabelNames{"__address__"},
- TargetLabel: "__tmp_hash",
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Modulus: 8,
- Separator: ";",
- Action: relabel.HashMod,
+ SourceLabels: model.LabelNames{"__address__"},
+ TargetLabel: "__tmp_hash",
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Modulus: 8,
+ Separator: ";",
+ Action: relabel.HashMod,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- SourceLabels: model.LabelNames{"__tmp_hash"},
- Regex: relabel.MustNewRegexp("1"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.Keep,
+ SourceLabels: model.LabelNames{"__tmp_hash"},
+ Regex: relabel.MustNewRegexp("1"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.Keep,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- Regex: relabel.MustNewRegexp("1"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.LabelMap,
+ Regex: relabel.MustNewRegexp("1"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.LabelMap,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- Regex: relabel.MustNewRegexp("d"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.LabelDrop,
+ Regex: relabel.MustNewRegexp("d"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.LabelDrop,
+ MetricNameValidationScheme: model.UTF8Validation,
},
{
- Regex: relabel.MustNewRegexp("k"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.LabelKeep,
+ Regex: relabel.MustNewRegexp("k"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.LabelKeep,
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
MetricRelabelConfigs: []*relabel.Config{
{
- SourceLabels: model.LabelNames{"__name__"},
- Regex: relabel.MustNewRegexp("expensive_metric.*"),
- Separator: ";",
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Action: relabel.Drop,
+ SourceLabels: model.LabelNames{"__name__"},
+ Regex: relabel.MustNewRegexp("expensive_metric.*"),
+ Separator: ";",
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Action: relabel.Drop,
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
},
@@ -478,12 +492,13 @@ var expectedConf = &Config{
RelabelConfigs: []*relabel.Config{
{
- SourceLabels: model.LabelNames{"__meta_sd_consul_tags"},
- Regex: relabel.MustNewRegexp("label:([^=]+)=([^,]+)"),
- Separator: ",",
- TargetLabel: "${1}",
- Replacement: "${2}",
- Action: relabel.Replace,
+ SourceLabels: model.LabelNames{"__meta_sd_consul_tags"},
+ Regex: relabel.MustNewRegexp("label:([^=]+)=([^,]+)"),
+ Separator: ",",
+ TargetLabel: "${1}",
+ Replacement: "${2}",
+ Action: relabel.Replace,
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
},
@@ -1275,12 +1290,13 @@ var expectedConf = &Config{
RelabelConfigs: []*relabel.Config{
{
- Action: relabel.Uppercase,
- Regex: relabel.DefaultRelabelConfig.Regex,
- Replacement: relabel.DefaultRelabelConfig.Replacement,
- Separator: relabel.DefaultRelabelConfig.Separator,
- SourceLabels: model.LabelNames{"instance"},
- TargetLabel: "instance",
+ Action: relabel.Uppercase,
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Separator: relabel.DefaultRelabelConfig.Separator,
+ SourceLabels: model.LabelNames{"instance"},
+ TargetLabel: "instance",
+ MetricNameValidationScheme: model.UTF8Validation,
},
},
diff --git a/config/testdata/remote_write_local_global_default_validation_mode.good.yml b/config/testdata/remote_write_local_global_default_validation_mode.good.yml
new file mode 100644
index 000000000..61745c6b9
--- /dev/null
+++ b/config/testdata/remote_write_local_global_default_validation_mode.good.yml
@@ -0,0 +1,9 @@
+global:
+ metric_name_validation_scheme: legacy
+remote_write:
+ - url: localhost:9201
+ write_relabel_configs:
+ - source_labels: [ "test" ]
+ regex: expensive.*
+ action: drop
+ target_label: "test("
diff --git a/config/testdata/remote_write_local_global_validation_mode.bad.yml b/config/testdata/remote_write_local_global_validation_mode.bad.yml
new file mode 100644
index 000000000..158507f1c
--- /dev/null
+++ b/config/testdata/remote_write_local_global_validation_mode.bad.yml
@@ -0,0 +1,10 @@
+global:
+ metric_name_validation_scheme: utf8
+remote_write:
+ - url: localhost:9201
+ metric_name_validation_scheme: legacy
+ write_relabel_configs:
+ - source_labels: [ "test" ]
+ regex: expensive.*
+ action: drop
+ target_label: "test("
diff --git a/config/testdata/remote_write_local_global_validation_mode.good.yml b/config/testdata/remote_write_local_global_validation_mode.good.yml
new file mode 100644
index 000000000..66312406e
--- /dev/null
+++ b/config/testdata/remote_write_local_global_validation_mode.good.yml
@@ -0,0 +1,10 @@
+global:
+ metric_name_validation_scheme: legacy
+remote_write:
+ - url: localhost:9201
+ metric_name_validation_scheme: utf8
+ write_relabel_configs:
+ - source_labels: [ "test" ]
+ regex: expensive.*
+ action: drop
+ target_label: "test("
diff --git a/docs/command-line/promtool.md b/docs/command-line/promtool.md
index 92e0ac003..a42a44118 100644
--- a/docs/command-line/promtool.md
+++ b/docs/command-line/promtool.md
@@ -60,6 +60,7 @@ Check the resources for validity.
| --- | --- | --- |
| --query.lookback-delta
| The server's maximum query lookback duration. | `5m` |
| --extended
| Print extended information related to the cardinality of the metrics. | |
+| --validation-scheme
| Validate metric and label names | `utf8` |
diff --git a/go.mod b/go.mod
index 0197f5cb0..ed90cd495 100644
--- a/go.mod
+++ b/go.mod
@@ -94,6 +94,10 @@ require (
k8s.io/klog/v2 v2.130.1
)
+replace github.com/prometheus/common => github.com/juliusmh/common v0.65.1-0.20250704080025-9663093ffd39
+
+replace github.com/prometheus/client_golang => github.com/juliusmh/client_golang v1.22.1-0.20250701110037-ceb5803cbf1f
+
require (
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
diff --git a/go.sum b/go.sum
index 0183fb256..7c59b70dc 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,7 @@ cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
@@ -37,10 +38,7 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -81,8 +79,6 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -105,6 +101,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -153,10 +150,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -183,7 +176,6 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
@@ -193,7 +185,6 @@ github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
@@ -204,8 +195,8 @@ github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwm
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
@@ -216,10 +207,11 @@ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -312,13 +304,14 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/juliusmh/client_golang v1.22.1-0.20250701110037-ceb5803cbf1f h1:UU3kYZyItj1WIp7nfjBrw+S9xjyEX1NbHgp1UP3Ov6U=
+github.com/juliusmh/client_golang v1.22.1-0.20250701110037-ceb5803cbf1f/go.mod h1:tF4MYJHY3axE4Wh1TgNU/klT0a4RUGthK8Chg9eU/sA=
+github.com/juliusmh/common v0.65.1-0.20250704080025-9663093ffd39 h1:TokVPDmuzyKegFXvv13oVnnlp/Hcd5TRl3g0HdiKwXQ=
+github.com/juliusmh/common v0.65.1-0.20250704080025-9663093ffd39/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -333,9 +326,7 @@ github.com/knadh/koanf/v2 v2.2.0 h1:FZFwd9bUjpb8DyCWARUBy5ovuhDs1lI87dOEn2K8UVU=
github.com/knadh/koanf/v2 v2.2.0/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -363,7 +354,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
@@ -397,15 +387,12 @@ github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6U
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM=
@@ -442,7 +429,7 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhM
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -457,29 +444,15 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/alertmanager v0.28.1 h1:BK5pCoAtaKg01BYRUJhEDV1tqJMEtYBGzPw8QdvnnvA=
github.com/prometheus/alertmanager v0.28.1/go.mod h1:0StpPUDDHi1VXeM7p2yYfeZgLVi/PPlt39vo9LQUHxM=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
-github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
-github.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3 h1:R/zO7ombSHCI8bjQusgCMSL+cE669w5/R2upq5WlPD0=
-github.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=
github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA=
github.com/prometheus/otlptranslator v0.0.0-20250620074007-94f535e0c588 h1:QlySqDdSESgWDePeAYskbbcKKdowI26m9aU9zloHyYE=
github.com/prometheus/otlptranslator v0.0.0-20250620074007-94f535e0c588/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/sigv4 v0.2.0 h1:qDFKnHYFswJxdzGeRP63c4HlH3Vbn1Yf/Ao2zabtVXk=
@@ -488,6 +461,8 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -499,8 +474,6 @@ github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY=
github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -509,7 +482,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stackitcloud/stackit-sdk-go/core v0.17.2 h1:jPyn+i8rkp2hM80+hOg0B/1EVRbMt778Tr5RWyK1m2E=
github.com/stackitcloud/stackit-sdk-go/core v0.17.2/go.mod h1:8KIw3czdNJ9sdil9QQimxjR6vHjeINFrRv0iZ67wfn0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
@@ -520,6 +492,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -533,6 +506,7 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -611,76 +585,113 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@@ -691,6 +702,10 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -707,11 +722,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
@@ -720,10 +734,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/model/labels/labels_common.go b/model/labels/labels_common.go
index 5f46d6c35..4e851e023 100644
--- a/model/labels/labels_common.go
+++ b/model/labels/labels_common.go
@@ -101,23 +101,16 @@ func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// IsValid checks if the metric name or label names are valid.
+//
+//nolint:forbidigo
func (ls Labels) IsValid(validationScheme model.ValidationScheme) bool {
err := ls.Validate(func(l Label) error {
if l.Name == model.MetricNameLabel {
- // If the default validation scheme has been overridden with legacy mode,
- // we need to call the special legacy validation checker.
- if validationScheme == model.LegacyValidation && !model.IsValidLegacyMetricName(string(model.LabelValue(l.Value))) {
- return strconv.ErrSyntax
- }
- if !model.IsValidMetricName(model.LabelValue(l.Value)) {
+ if !model.IsValidMetricName(model.LabelValue(l.Value), validationScheme) {
return strconv.ErrSyntax
}
}
- if validationScheme == model.LegacyValidation {
- if !model.LabelName(l.Name).IsValidLegacy() || !model.LabelValue(l.Value).IsValid() {
- return strconv.ErrSyntax
- }
- } else if !model.LabelName(l.Name).IsValid() || !model.LabelValue(l.Value).IsValid() {
+ if !model.LabelName(l.Name).IsValid(validationScheme) || !model.LabelValue(l.Value).IsValid() {
return strconv.ErrSyntax
}
return nil
diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go
index 70daef426..809035da1 100644
--- a/model/relabel/relabel.go
+++ b/model/relabel/relabel.go
@@ -14,6 +14,7 @@
package relabel
import (
+ "cmp"
"crypto/md5"
"encoding/binary"
"encoding/json"
@@ -100,6 +101,12 @@ type Config struct {
Replacement string `yaml:"replacement,omitempty" json:"replacement,omitempty"`
// Action is the action to be performed for the relabeling.
Action Action `yaml:"action,omitempty" json:"action,omitempty"`
+ // MetricNameValidationScheme to use when validating labels.
+ MetricNameValidationScheme model.ValidationScheme `yaml:"metric_name_validation_scheme,omitempty" json:"metricNameValidationScheme,omitempty"`
+}
+
+func (c Config) validationScheme() model.ValidationScheme {
+ return cmp.Or(c.MetricNameValidationScheme, model.UTF8Validation)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@@ -112,10 +119,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if c.Regex.Regexp == nil {
c.Regex = MustNewRegexp("")
}
- return c.Validate()
+ return nil
}
-func (c *Config) Validate() error {
+func (c *Config) Validate(defaultValidationScheme model.ValidationScheme) error {
if c.Action == "" {
return errors.New("relabel action cannot be empty")
}
@@ -125,29 +132,28 @@ func (c *Config) Validate() error {
if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
}
- if c.Action == Replace && !varInRegexTemplate(c.TargetLabel) && !model.LabelName(c.TargetLabel).IsValid() {
- return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
+
+ if defaultValidationScheme != model.UnsetValidation && c.MetricNameValidationScheme == model.UnsetValidation {
+ c.MetricNameValidationScheme = defaultValidationScheme
}
- isValidLabelNameWithRegexVarFn := func(value string) bool {
- // UTF-8 allows ${} characters, so standard validation allow $variables by default.
- // TODO(bwplotka): Relabelling users cannot put $ and ${<...>} characters in metric names or values.
- // Design escaping mechanism to allow that, once valid use case appears.
- return model.LabelName(value).IsValid()
+ if c.Action == Replace && !varInRegexTemplate(c.TargetLabel) && !model.LabelName(c.TargetLabel).IsValid(c.validationScheme()) {
+ return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
- if c.Action == Replace && varInRegexTemplate(c.TargetLabel) && !isValidLabelNameWithRegexVarFn(c.TargetLabel) {
+
+ if c.Action == Replace && varInRegexTemplate(c.TargetLabel) && !model.LabelName(c.TargetLabel).IsValid(c.validationScheme()) {
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
- if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !model.LabelName(c.TargetLabel).IsValid() {
+ if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !model.LabelName(c.TargetLabel).IsValid(c.validationScheme()) {
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("'replacement' can not be set for %s action", c.Action)
}
- if c.Action == LabelMap && !isValidLabelNameWithRegexVarFn(c.Replacement) {
+ if c.Action == LabelMap && !model.LabelName(c.Replacement).IsValid(c.validationScheme()) {
return fmt.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action)
}
- if c.Action == HashMod && !model.LabelName(c.TargetLabel).IsValid() {
+ if c.Action == HashMod && !model.LabelName(c.TargetLabel).IsValid(c.validationScheme()) {
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
}
@@ -319,7 +325,7 @@ func relabel(cfg *Config, lb *labels.Builder) (keep bool) {
break
}
target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
- if !target.IsValid() {
+ if !target.IsValid(cfg.validationScheme()) {
break
}
res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go
index 845e304f3..16238e449 100644
--- a/model/relabel/relabel_test.go
+++ b/model/relabel/relabel_test.go
@@ -732,51 +732,57 @@ func TestRelabel(t *testing.T) {
},
}
- for _, test := range tests {
- // Setting default fields, mimicking the behaviour in Prometheus.
- for _, cfg := range test.relabel {
- if cfg.Action == "" {
- cfg.Action = DefaultRelabelConfig.Action
- }
- if cfg.Separator == "" {
- cfg.Separator = DefaultRelabelConfig.Separator
- }
- if cfg.Regex.Regexp == nil || cfg.Regex.String() == "" {
- cfg.Regex = DefaultRelabelConfig.Regex
- }
- if cfg.Replacement == "" {
- cfg.Replacement = DefaultRelabelConfig.Replacement
+ for i, test := range tests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ // Setting default fields, mimicking the behaviour in Prometheus.
+ for _, cfg := range test.relabel {
+ if cfg.Action == "" {
+ cfg.Action = DefaultRelabelConfig.Action
+ }
+ if cfg.Separator == "" {
+ cfg.Separator = DefaultRelabelConfig.Separator
+ }
+ if cfg.Regex.Regexp == nil || cfg.Regex.String() == "" {
+ cfg.Regex = DefaultRelabelConfig.Regex
+ }
+ if cfg.Replacement == "" {
+ cfg.Replacement = DefaultRelabelConfig.Replacement
+ }
+ require.NoError(t, cfg.Validate(model.UTF8Validation))
}
- require.NoError(t, cfg.Validate())
- }
- res, keep := Process(test.input, test.relabel...)
- require.Equal(t, !test.drop, keep)
- if keep {
- testutil.RequireEqual(t, test.output, res)
- }
+ res, keep := Process(test.input, test.relabel...)
+ require.Equal(t, !test.drop, keep)
+ if keep {
+ testutil.RequireEqual(t, test.output, res)
+ }
+ })
}
}
func TestRelabelValidate(t *testing.T) {
tests := []struct {
config Config
+ scheme model.ValidationScheme
expected string
}{
{
config: Config{},
+ scheme: model.LegacyValidation,
expected: `relabel action cannot be empty`,
},
{
config: Config{
Action: Replace,
},
+ scheme: model.LegacyValidation,
expected: `requires 'target_label' value`,
},
{
config: Config{
Action: Lowercase,
},
+ scheme: model.LegacyValidation,
expected: `requires 'target_label' value`,
},
{
@@ -785,6 +791,33 @@ func TestRelabelValidate(t *testing.T) {
Replacement: DefaultRelabelConfig.Replacement,
TargetLabel: "${3}", // With UTF-8 naming, this is now a legal relabel rule.
},
+ scheme: model.UnsetValidation, // defaults to UTF8 validation
+ },
+ {
+ config: Config{
+ Action: Lowercase,
+ Replacement: DefaultRelabelConfig.Replacement,
+ TargetLabel: "${3}", // Fails with legacy validation
+ },
+ scheme: model.LegacyValidation,
+ expected: "\"${3}\" is invalid 'target_label' for lowercase action",
+ },
+ {
+ config: Config{
+ Action: Lowercase,
+ Replacement: DefaultRelabelConfig.Replacement,
+ TargetLabel: "${3}", // With UTF-8 naming, this is now a legal relabel rule.
+ MetricNameValidationScheme: model.UTF8Validation,
+ },
+ scheme: model.LegacyValidation, // overridden by the config's scheme
+ },
+ {
+ config: Config{
+ Action: Lowercase,
+ Replacement: DefaultRelabelConfig.Replacement,
+ TargetLabel: "${3}", // With UTF-8 naming, this is now a legal relabel rule.
+ },
+ scheme: model.UTF8Validation,
},
{
config: Config{
@@ -794,6 +827,7 @@ func TestRelabelValidate(t *testing.T) {
Replacement: "${1}",
TargetLabel: "${3}",
},
+ scheme: model.UTF8Validation,
},
{
config: Config{
@@ -803,6 +837,7 @@ func TestRelabelValidate(t *testing.T) {
Replacement: "${1}",
TargetLabel: "0${3}", // With UTF-8 naming this targets a valid label.
},
+ scheme: model.UTF8Validation,
},
{
config: Config{
@@ -812,11 +847,12 @@ func TestRelabelValidate(t *testing.T) {
Replacement: "${1}",
TargetLabel: "-${3}", // With UTF-8 naming this targets a valid label.
},
+ scheme: model.UTF8Validation,
},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
- err := test.config.Validate()
+ err := test.config.Validate(test.scheme)
if test.expected == "" {
require.NoError(t, err)
} else {
diff --git a/model/rulefmt/rulefmt.go b/model/rulefmt/rulefmt.go
index 96b70cc66..79f959c82 100644
--- a/model/rulefmt/rulefmt.go
+++ b/model/rulefmt/rulefmt.go
@@ -96,7 +96,7 @@ type ruleGroups struct {
}
// Validate validates all rules in the rule groups.
-func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
+func (g *RuleGroups) Validate(node ruleGroups, validationScheme model.ValidationScheme) (errs []error) {
set := map[string]struct{}{}
for j, g := range g.Groups {
@@ -112,7 +112,7 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
}
for k, v := range g.Labels {
- if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
+ if !model.LabelName(k).IsValid(validationScheme) || k == model.MetricNameLabel {
errs = append(
errs, fmt.Errorf("invalid label name: %s", k),
)
@@ -128,7 +128,7 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
set[g.Name] = struct{}{}
for i, r := range g.Rules {
- for _, node := range r.Validate(node.Groups[j].Rules[i]) {
+ for _, node := range r.Validate(node.Groups[j].Rules[i], validationScheme) {
var ruleName string
if r.Alert != "" {
ruleName = r.Alert
@@ -198,7 +198,7 @@ type RuleNode struct {
}
// Validate the rule and return a list of encountered errors.
-func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
+func (r *Rule) Validate(node RuleNode, validationScheme model.ValidationScheme) (nodes []WrappedError) {
if r.Record != "" && r.Alert != "" {
nodes = append(nodes, WrappedError{
err: errors.New("only one of 'record' and 'alert' must be set"),
@@ -244,7 +244,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
node: &node.Record,
})
}
- if !model.IsValidMetricName(model.LabelValue(r.Record)) {
+ if !model.IsValidMetricName(model.LabelValue(r.Record), validationScheme) {
nodes = append(nodes, WrappedError{
err: fmt.Errorf("invalid recording rule name: %s", r.Record),
node: &node.Record,
@@ -261,7 +261,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
}
for k, v := range r.Labels {
- if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
+ if !model.LabelName(k).IsValid(validationScheme) || k == model.MetricNameLabel {
nodes = append(nodes, WrappedError{
err: fmt.Errorf("invalid label name: %s", k),
})
@@ -275,7 +275,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
}
for k := range r.Annotations {
- if !model.LabelName(k).IsValid() {
+ if !model.LabelName(k).IsValid(validationScheme) {
nodes = append(nodes, WrappedError{
err: fmt.Errorf("invalid annotation name: %s", k),
})
@@ -338,8 +338,27 @@ func testTemplateParsing(rl *Rule) (errs []error) {
return errs
}
+type parseArgs struct {
+ namingScheme model.ValidationScheme
+}
+
+type ParseOption func(*parseArgs)
+
+func WithNamingScheme(scheme model.ValidationScheme) ParseOption {
+ return func(args *parseArgs) {
+ args.namingScheme = scheme
+ }
+}
+
// Parse parses and validates a set of rules.
-func Parse(content []byte, ignoreUnknownFields bool) (*RuleGroups, []error) {
+func Parse(content []byte, ignoreUnknownFields bool, opts ...ParseOption) (*RuleGroups, []error) {
+ args := &parseArgs{
+ namingScheme: model.UTF8Validation,
+ }
+ for _, opt := range opts {
+ opt(args)
+ }
+
var (
groups RuleGroups
node ruleGroups
@@ -364,16 +383,16 @@ func Parse(content []byte, ignoreUnknownFields bool) (*RuleGroups, []error) {
return nil, errs
}
- return &groups, groups.Validate(node)
+ return &groups, groups.Validate(node, args.namingScheme)
}
// ParseFile reads and parses rules from a file.
-func ParseFile(file string, ignoreUnknownFields bool) (*RuleGroups, []error) {
+func ParseFile(file string, ignoreUnknownFields bool, opts ...ParseOption) (*RuleGroups, []error) {
b, err := os.ReadFile(file)
if err != nil {
return nil, []error{fmt.Errorf("%s: %w", file, err)}
}
- rgs, errs := Parse(b, ignoreUnknownFields)
+ rgs, errs := Parse(b, ignoreUnknownFields, opts...)
for i := range errs {
errs[i] = fmt.Errorf("%s: %w", file, errs[i])
}
diff --git a/model/rulefmt/rulefmt_test.go b/model/rulefmt/rulefmt_test.go
index 724b0cea8..4e89bc7b7 100644
--- a/model/rulefmt/rulefmt_test.go
+++ b/model/rulefmt/rulefmt_test.go
@@ -66,6 +66,7 @@ func TestParseFileFailure(t *testing.T) {
for _, c := range []struct {
filename string
errMsg string
+ opts []ParseOption
}{
{
filename: "duplicate_grp.bad.yaml",
@@ -107,9 +108,14 @@ func TestParseFileFailure(t *testing.T) {
filename: "record_and_keep_firing_for.bad.yaml",
errMsg: "invalid field 'keep_firing_for' in recording rule",
},
+ {
+ filename: "utf-8_annotation.bad.yaml",
+ opts: []ParseOption{WithNamingScheme(model.LegacyValidation)},
+ errMsg: "invalid annotation name: ins-tance",
+ },
} {
t.Run(c.filename, func(t *testing.T) {
- _, errs := ParseFile(filepath.Join("testdata", c.filename), false)
+ _, errs := ParseFile(filepath.Join("testdata", c.filename), false, c.opts...)
require.NotEmpty(t, errs, "Expected error parsing %s but got none", c.filename)
require.ErrorContainsf(t, errs[0], c.errMsg, "Expected error for %s.", c.filename)
})
diff --git a/model/rulefmt/testdata/utf-8_annotation.bad.yaml b/model/rulefmt/testdata/utf-8_annotation.bad.yaml
new file mode 100644
index 000000000..a747827a9
--- /dev/null
+++ b/model/rulefmt/testdata/utf-8_annotation.bad.yaml
@@ -0,0 +1,7 @@
+groups:
+ - name: yolo
+ rules:
+ - alert: hola
+ expr: 1
+ annotations:
+ ins-tance: localhost
diff --git a/model/textparse/benchmark_test.go b/model/textparse/benchmark_test.go
index e15a6b384..9a16f2972 100644
--- a/model/textparse/benchmark_test.go
+++ b/model/textparse/benchmark_test.go
@@ -240,7 +240,7 @@ func benchExpFmt(b *testing.B, data []byte, expFormatTypeStr string) {
}
for {
- if err := sdec.Decode(&decSamples); err != nil {
+ if err := sdec.Decode(&decSamples, model.UTF8Validation); err != nil {
if errors.Is(err, io.EOF) {
break
}
diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go
index 2ca6c03af..fd93a6397 100644
--- a/model/textparse/protobufparse.go
+++ b/model/textparse/protobufparse.go
@@ -80,11 +80,23 @@ type ProtobufParser struct {
// native histogram.
parseClassicHistograms bool
enableTypeAndUnitLabels bool
+
+ validationScheme model.ValidationScheme
+}
+
+// ProtobufParserOption is an option that can be applied to ProtobufParser.
+type ProtobufParserOption func(p *ProtobufParser)
+
+// WithValidationScheme sets the label/metric name validation. Default is UTF8Validation.
+func WithValidationScheme(scheme model.ValidationScheme) ProtobufParserOption {
+ return func(p *ProtobufParser) {
+ p.validationScheme = scheme
+ }
}
// NewProtobufParser returns a parser for the payload in the byte slice.
-func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels bool, st *labels.SymbolTable) Parser {
- return &ProtobufParser{
+func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels bool, st *labels.SymbolTable, opts ...ProtobufParserOption) Parser {
+ pp := &ProtobufParser{
dec: dto.NewMetricStreamingDecoder(b),
entryBytes: &bytes.Buffer{},
builder: labels.NewScratchBuilderWithSymbolTable(st, 16), // TODO(bwplotka): Try base builder.
@@ -92,7 +104,12 @@ func NewProtobufParser(b []byte, parseClassicHistograms, enableTypeAndUnitLabels
state: EntryInvalid,
parseClassicHistograms: parseClassicHistograms,
enableTypeAndUnitLabels: enableTypeAndUnitLabels,
+ validationScheme: model.UTF8Validation,
+ }
+ for _, opt := range opts {
+ opt(pp)
}
+ return pp
}
// Series returns the bytes of a series with a simple float64 as a
@@ -428,7 +445,7 @@ func (p *ProtobufParser) Next() (Entry, error) {
// We are at the beginning of a metric family. Put only the name
// into entryBytes and validate only name, help, and type for now.
name := p.dec.GetName()
- if !model.IsValidMetricName(model.LabelValue(name)) {
+ if !model.IsValidMetricName(model.LabelValue(name), p.validationScheme) {
return EntryInvalid, fmt.Errorf("invalid metric name: %s", name)
}
if help := p.dec.GetHelp(); !utf8.ValidString(help) {
diff --git a/prompb/io/prometheus/client/decoder.go b/prompb/io/prometheus/client/decoder.go
index d4fb4204c..d45d9728b 100644
--- a/prompb/io/prometheus/client/decoder.go
+++ b/prompb/io/prometheus/client/decoder.go
@@ -40,6 +40,19 @@ type MetricStreamingDecoder struct {
mData []byte
labels []pos
+
+ validationScheme model.ValidationScheme
+}
+
+// Option for the MetricStreamingDecoder.
+type Option func(*MetricStreamingDecoder)
+
+// WithValidationScheme sets the label/metric name validation scheme. Defaults to
+// UTF8Validation.
+func WithValidationScheme(scheme model.ValidationScheme) Option {
+ return func(m *MetricStreamingDecoder) {
+ m.validationScheme = scheme
+ }
}
// NewMetricStreamingDecoder returns a Go iterator that unmarshals given protobuf bytes one
@@ -51,13 +64,18 @@ type MetricStreamingDecoder struct {
// method to use when checking the value.
//
// TODO(bwplotka): io.Reader approach is possible too, but textparse has access to whole scrape for now.
-func NewMetricStreamingDecoder(data []byte) *MetricStreamingDecoder {
- return &MetricStreamingDecoder{
- in: data,
- MetricFamily: &MetricFamily{},
- Metric: &Metric{},
- metrics: make([]pos, 0, 100),
+func NewMetricStreamingDecoder(data []byte, opts ...Option) *MetricStreamingDecoder {
+ msd := &MetricStreamingDecoder{
+ in: data,
+ MetricFamily: &MetricFamily{},
+ Metric: &Metric{},
+ metrics: make([]pos, 0, 100),
+ validationScheme: model.UTF8Validation,
+ }
+ for _, opt := range opts {
+ opt(msd)
}
+ return msd
}
var errInvalidVarint = errors.New("clientpb: invalid varint encountered")
@@ -162,7 +180,7 @@ type scratchBuilder interface {
// structs tailored for streaming decoding.
func (m *MetricStreamingDecoder) Label(b scratchBuilder) error {
for _, l := range m.labels {
- if err := parseLabel(m.mData[l.start:l.end], b); err != nil {
+ if err := parseLabel(m.mData[l.start:l.end], b, m.validationScheme); err != nil {
return err
}
}
@@ -171,7 +189,7 @@ func (m *MetricStreamingDecoder) Label(b scratchBuilder) error {
// parseLabel is essentially LabelPair.Unmarshal but directly adding into scratch builder
// and reusing strings.
-func parseLabel(dAtA []byte, b scratchBuilder) error {
+func parseLabel(dAtA []byte, b scratchBuilder, validationScheme model.ValidationScheme) error {
var name, value string
l := len(dAtA)
iNdEx := 0
@@ -232,7 +250,7 @@ func parseLabel(dAtA []byte, b scratchBuilder) error {
return io.ErrUnexpectedEOF
}
name = yoloString(dAtA[iNdEx:postIndex])
- if !model.LabelName(name).IsValid() {
+ if !model.LabelName(name).IsValid(validationScheme) {
return fmt.Errorf("invalid label name: %s", name)
}
iNdEx = postIndex
diff --git a/promql/engine.go b/promql/engine.go
index 275fc34a4..8fc26ebe8 100644
--- a/promql/engine.go
+++ b/promql/engine.go
@@ -156,14 +156,17 @@ type PrometheusQueryOpts struct {
enablePerStepStats bool
// Lookback delta duration for this query.
lookbackDelta time.Duration
+ // validationScheme for metric/label names.
+ validationScheme model.ValidationScheme
}
var _ QueryOpts = &PrometheusQueryOpts{}
-func NewPrometheusQueryOpts(enablePerStepStats bool, lookbackDelta time.Duration) QueryOpts {
+func NewPrometheusQueryOpts(enablePerStepStats bool, lookbackDelta time.Duration, validationScheme model.ValidationScheme) QueryOpts {
return &PrometheusQueryOpts{
enablePerStepStats: enablePerStepStats,
lookbackDelta: lookbackDelta,
+ validationScheme: validationScheme,
}
}
@@ -175,11 +178,17 @@ func (p *PrometheusQueryOpts) LookbackDelta() time.Duration {
return p.lookbackDelta
}
+func (p *PrometheusQueryOpts) ValidationScheme() model.ValidationScheme {
+ return p.validationScheme
+}
+
type QueryOpts interface {
// Enables recording per-step statistics if the engine has it enabled as well. Disabled by default.
EnablePerStepStats() bool
// Lookback delta duration for this query.
LookbackDelta() time.Duration
+ // ValidationScheme to use for label and metric names.
+ ValidationScheme() model.ValidationScheme
}
// query implements the Query interface.
@@ -198,6 +207,8 @@ type query struct {
matrix Matrix
// Cancellation function for the query.
cancel func()
+ // validationScheme used for label and metric names.
+ validationScheme model.ValidationScheme
// The engine against which the query is executed.
ng *Engine
@@ -525,7 +536,7 @@ func (ng *Engine) NewRangeQuery(ctx context.Context, q storage.Queryable, opts Q
func (ng *Engine) newQuery(q storage.Queryable, qs string, opts QueryOpts, start, end time.Time, interval time.Duration) (*parser.Expr, *query) {
if opts == nil {
- opts = NewPrometheusQueryOpts(false, 0)
+ opts = NewPrometheusQueryOpts(false, 0, model.UTF8Validation)
}
lookbackDelta := opts.LookbackDelta()
@@ -540,12 +551,13 @@ func (ng *Engine) newQuery(q storage.Queryable, qs string, opts QueryOpts, start
LookbackDelta: lookbackDelta,
}
qry := &query{
- q: qs,
- stmt: es,
- ng: ng,
- stats: stats.NewQueryTimers(),
- sampleStats: stats.NewQuerySamples(ng.enablePerStepStats && opts.EnablePerStepStats()),
- queryable: q,
+ q: qs,
+ stmt: es,
+ ng: ng,
+ stats: stats.NewQueryTimers(),
+ sampleStats: stats.NewQuerySamples(ng.enablePerStepStats && opts.EnablePerStepStats()),
+ queryable: q,
+ validationScheme: opts.ValidationScheme(),
}
return &es.Expr, qry
}
@@ -750,6 +762,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
enableDelayedNameRemoval: ng.enableDelayedNameRemoval,
enableTypeAndUnitLabels: ng.enableTypeAndUnitLabels,
querier: querier,
+ validationScheme: query.validationScheme,
}
query.sampleStats.InitStepTracking(start, start, 1)
@@ -810,6 +823,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
enableDelayedNameRemoval: ng.enableDelayedNameRemoval,
enableTypeAndUnitLabels: ng.enableTypeAndUnitLabels,
querier: querier,
+ validationScheme: query.validationScheme,
}
query.sampleStats.InitStepTracking(evaluator.startTimestamp, evaluator.endTimestamp, evaluator.interval)
val, warnings, err := evaluator.Eval(ctxInnerEval, s.Expr)
@@ -1084,6 +1098,7 @@ type evaluator struct {
enableDelayedNameRemoval bool
enableTypeAndUnitLabels bool
querier storage.Querier
+ validationScheme model.ValidationScheme
}
// errorf causes a panic with the input formatted into an error.
@@ -1671,7 +1686,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
if e.Op == parser.COUNT_VALUES {
valueLabel := param.(*parser.StringLiteral)
- if !model.LabelName(valueLabel.Val).IsValid() {
+ if !model.LabelName(valueLabel.Val).IsValid(ev.validationScheme) {
ev.errorf("invalid label name %s", valueLabel)
}
if !e.Without {
@@ -2077,6 +2092,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
enableDelayedNameRemoval: ev.enableDelayedNameRemoval,
enableTypeAndUnitLabels: ev.enableTypeAndUnitLabels,
querier: ev.querier,
+ validationScheme: ev.validationScheme,
}
if e.Step != 0 {
@@ -2123,6 +2139,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
enableDelayedNameRemoval: ev.enableDelayedNameRemoval,
enableTypeAndUnitLabels: ev.enableTypeAndUnitLabels,
querier: ev.querier,
+ validationScheme: ev.validationScheme,
}
res, ws := newEv.eval(ctx, e.Expr)
ev.currentSamples = newEv.currentSamples
diff --git a/promql/engine_test.go b/promql/engine_test.go
index ce5ef6efd..09828f06c 100644
--- a/promql/engine_test.go
+++ b/promql/engine_test.go
@@ -26,6 +26,7 @@ import (
"testing"
"time"
+ "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
@@ -1301,7 +1302,7 @@ load 10s
for _, c := range cases {
t.Run(c.Query, func(t *testing.T) {
- opts := promql.NewPrometheusQueryOpts(true, 0)
+ opts := promql.NewPrometheusQueryOpts(true, 0, model.UTF8Validation)
engine := promqltest.NewTestEngine(t, true, 0, promqltest.DefaultMaxSamplesPerQuery)
runQuery := func(expErr error) *stats.Statistics {
@@ -3343,7 +3344,7 @@ metric 0 1 2
storage := promqltest.LoadedStorage(t, load)
t.Cleanup(func() { storage.Close() })
- opts := promql.NewPrometheusQueryOpts(false, c.queryLookback)
+ opts := promql.NewPrometheusQueryOpts(false, c.queryLookback, model.UTF8Validation)
qry, err := engine.NewInstantQuery(context.Background(), storage, opts, query, c.ts)
require.NoError(t, err)
diff --git a/promql/functions.go b/promql/functions.go
index 9af904c9e..fb6300895 100644
--- a/promql/functions.go
+++ b/promql/functions.go
@@ -1576,7 +1576,7 @@ func (ev *evaluator) evalLabelReplace(ctx context.Context, args parser.Expressio
if err != nil {
panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
}
- if !model.LabelName(dst).IsValid() {
+ if !model.LabelName(dst).IsValid(ev.validationScheme) {
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
}
@@ -1624,12 +1624,12 @@ func (ev *evaluator) evalLabelJoin(ctx context.Context, args parser.Expressions)
)
for i := 3; i < len(args); i++ {
src := stringFromArg(args[i])
- if !model.LabelName(src).IsValid() {
+ if !model.LabelName(src).IsValid(ev.validationScheme) {
panic(fmt.Errorf("invalid source label name in label_join(): %s", src))
}
srcLabels[i-3] = src
}
- if !model.LabelName(dst).IsValid() {
+ if !model.LabelName(dst).IsValid(ev.validationScheme) {
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
}
diff --git a/promql/parser/generated_parser.y b/promql/parser/generated_parser.y
index e7e16cd03..155a982b2 100644
--- a/promql/parser/generated_parser.y
+++ b/promql/parser/generated_parser.y
@@ -378,14 +378,14 @@ grouping_label_list:
grouping_label : maybe_label
{
- if !model.LabelName($1.Val).IsValid() {
+ if !model.LabelName($1.Val).IsValid(yylex.(*parser).validationScheme) {
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", $1.Val)
}
$$ = $1
}
| STRING {
unquoted := yylex.(*parser).unquoteString($1.Val)
- if !model.LabelName(unquoted).IsValid() {
+ if !model.LabelName(unquoted).IsValid(yylex.(*parser).validationScheme) {
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", unquoted)
}
$$ = $1
diff --git a/promql/parser/generated_parser.y.go b/promql/parser/generated_parser.y.go
index e93d1b3de..14c0f4fae 100644
--- a/promql/parser/generated_parser.y.go
+++ b/promql/parser/generated_parser.y.go
@@ -1327,7 +1327,7 @@ yydefault:
case 59:
yyDollar = yyS[yypt-1 : yypt+1]
{
- if !model.LabelName(yyDollar[1].item.Val).IsValid() {
+ if !model.LabelName(yyDollar[1].item.Val).IsValid(yylex.(*parser).validationScheme) {
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", yyDollar[1].item.Val)
}
yyVAL.item = yyDollar[1].item
@@ -1336,7 +1336,7 @@ yydefault:
yyDollar = yyS[yypt-1 : yypt+1]
{
unquoted := yylex.(*parser).unquoteString(yyDollar[1].item.Val)
- if !model.LabelName(unquoted).IsValid() {
+ if !model.LabelName(unquoted).IsValid(yylex.(*parser).validationScheme) {
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", unquoted)
}
yyVAL.item = yyDollar[1].item
diff --git a/promql/parser/parse.go b/promql/parser/parse.go
index 03c6a8446..bff6bd530 100644
--- a/promql/parser/parse.go
+++ b/promql/parser/parse.go
@@ -71,6 +71,8 @@ type parser struct {
generatedParserResult interface{}
parseErrors ParseErrors
+
+ validationScheme model.ValidationScheme
}
type Opt func(p *parser)
@@ -81,6 +83,14 @@ func WithFunctions(functions map[string]*Function) Opt {
}
}
+// WithValidationScheme controls how label/metric names are validated.
+// Defaults to UTF8Validation.
+func WithValidationScheme(scheme model.ValidationScheme) Opt {
+ return func(p *parser) {
+ p.validationScheme = scheme
+ }
+}
+
// NewParser returns a new parser.
func NewParser(input string, opts ...Opt) *parser { //nolint:revive // unexported-return
p := parserPool.Get().(*parser)
@@ -90,6 +100,7 @@ func NewParser(input string, opts ...Opt) *parser { //nolint:revive // unexporte
p.parseErrors = nil
p.generatedParserResult = nil
p.closingParens = make([]posrange.Pos, 0)
+ p.validationScheme = model.UTF8Validation
// Clear lexer struct before reusing.
p.lex = Lexer{
diff --git a/scrape/scrape.go b/scrape/scrape.go
index 34f74b549..842b07090 100644
--- a/scrape/scrape.go
+++ b/scrape/scrape.go
@@ -16,6 +16,7 @@ package scrape
import (
"bufio"
"bytes"
+ "cmp"
"context"
"errors"
"fmt"
@@ -321,7 +322,7 @@ func (sp *scrapePool) reload(cfg *config.ScrapeConfig) error {
sp.config = cfg
oldClient := sp.client
sp.client = client
- sp.validationScheme = cfg.MetricNameValidationScheme
+ sp.validationScheme = cmp.Or(cfg.MetricNameValidationScheme, model.UTF8Validation)
var escapingScheme model.EscapingScheme
escapingScheme, err = model.ToEscapingScheme(cfg.MetricNameEscapingScheme)
if err != nil {
diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go
index 6fbf44ee8..24a652108 100644
--- a/scrape/scrape_test.go
+++ b/scrape/scrape_test.go
@@ -1476,7 +1476,7 @@ func TestPromTextToProto(t *testing.T) {
var got []string
for {
mf := &dto.MetricFamily{}
- if err := d.Decode(mf); err != nil {
+ if err := d.Decode(mf, model.UTF8Validation); err != nil {
if errors.Is(err, io.EOF) {
break
}
diff --git a/storage/remote/client.go b/storage/remote/client.go
index 68891f659..5f47f58ad 100644
--- a/storage/remote/client.go
+++ b/storage/remote/client.go
@@ -15,6 +15,7 @@ package remote
import (
"bytes"
+ "cmp"
"context"
"errors"
"fmt"
@@ -121,6 +122,8 @@ type Client struct {
writeProtoMsg config.RemoteWriteProtoMsg
writeCompression compression.Type // Not exposed by ClientConfig for now.
+
+ validationScheme model.ValidationScheme
}
// ClientConfig configures a client.
@@ -136,6 +139,8 @@ type ClientConfig struct {
WriteProtoMsg config.RemoteWriteProtoMsg
ChunkedReadLimit uint64
RoundRobinDNS bool
+ // ValidationScheme for label and metric names. Defaults to utf8.
+ ValidationScheme model.ValidationScheme
}
// ReadClient will request the STREAMED_XOR_CHUNKS method of remote read but can
@@ -166,6 +171,7 @@ func NewReadClient(name string, conf *ClientConfig, optFuncs ...config_util.HTTP
readQueries: remoteReadQueries.WithLabelValues(name, conf.URL.String()),
readQueriesTotal: remoteReadQueriesTotal.MustCurryWith(prometheus.Labels{remoteName: name, endpoint: conf.URL.String()}),
readQueriesDuration: remoteReadQueryDuration.MustCurryWith(prometheus.Labels{remoteName: name, endpoint: conf.URL.String()}),
+ validationScheme: cmp.Or(conf.ValidationScheme, model.UTF8Validation),
}, nil
}
@@ -446,5 +452,5 @@ func (c *Client) handleSampledResponse(req *prompb.ReadRequest, httpResp *http.R
// This client does not batch queries so there's always only 1 result.
res := resp.Results[0]
- return FromQueryResult(sortSeries, res), nil
+ return FromQueryResult(sortSeries, res, WithNameValidation(c.validationScheme)), nil
}
diff --git a/storage/remote/codec.go b/storage/remote/codec.go
index 80bb81150..5639efbd7 100644
--- a/storage/remote/codec.go
+++ b/storage/remote/codec.go
@@ -173,12 +173,34 @@ func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult,
return resp, ss.Warnings(), ss.Err()
}
+type fromQueryResultArgs struct {
+ nameValidation model.ValidationScheme
+}
+
+// FromQueryResultOption is an option for creating QueryResults.
+type FromQueryResultOption func(*fromQueryResultArgs)
+
+// WithNameValidation sets the label/metric name validation scheme.
+// Defaults to UTF8Validation.
+func WithNameValidation(nameValidation model.ValidationScheme) FromQueryResultOption {
+ return func(args *fromQueryResultArgs) {
+ args.nameValidation = nameValidation
+ }
+}
+
// FromQueryResult unpacks and sorts a QueryResult proto.
-func FromQueryResult(sortSeries bool, res *prompb.QueryResult) storage.SeriesSet {
+func FromQueryResult(sortSeries bool, res *prompb.QueryResult, opts ...FromQueryResultOption) storage.SeriesSet {
+ args := &fromQueryResultArgs{
+ nameValidation: model.UTF8Validation,
+ }
+ for _, opt := range opts {
+ opt(args)
+ }
+
b := labels.NewScratchBuilder(0)
series := make([]storage.Series, 0, len(res.Timeseries))
for _, ts := range res.Timeseries {
- if err := validateLabelsAndMetricName(ts.Labels); err != nil {
+ if err := validateLabelsAndMetricName(ts.Labels, args.nameValidation); err != nil {
return errSeriesSet{err: err}
}
lbls := ts.ToLabels(&b, nil)
@@ -756,12 +778,12 @@ func (it *chunkedSeriesIterator) Err() error {
// validateLabelsAndMetricName validates the label names/values and metric names returned from remote read,
// also making sure that there are no labels with duplicate names.
-func validateLabelsAndMetricName(ls []prompb.Label) error {
+func validateLabelsAndMetricName(ls []prompb.Label, validationScheme model.ValidationScheme) error {
for i, l := range ls {
- if l.Name == labels.MetricName && !model.IsValidMetricName(model.LabelValue(l.Value)) {
+ if l.Name == labels.MetricName && !model.IsValidMetricName(model.LabelValue(l.Value), validationScheme) {
return fmt.Errorf("invalid metric name: %v", l.Value)
}
- if !model.LabelName(l.Name).IsValid() {
+ if !model.LabelName(l.Name).IsValid(validationScheme) {
return fmt.Errorf("invalid label name: %v", l.Name)
}
if !model.LabelValue(l.Value).IsValid() {
diff --git a/storage/remote/codec_test.go b/storage/remote/codec_test.go
index c92f0f8cd..fb23a11d5 100644
--- a/storage/remote/codec_test.go
+++ b/storage/remote/codec_test.go
@@ -216,86 +216,112 @@ func TestWriteV2RequestFixture(t *testing.T) {
func TestValidateLabelsAndMetricName(t *testing.T) {
tests := []struct {
- input []prompb.Label
- expectedErr string
- description string
+ input []prompb.Label
+ expectedErr string
+ description string
+ validationScheme model.ValidationScheme
}{
{
input: []prompb.Label{
{Name: "__name__", Value: "name"},
{Name: "labelName", Value: "labelValue"},
},
- expectedErr: "",
- description: "regular labels",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "",
+ description: "regular labels",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "name"},
{Name: "_labelName", Value: "labelValue"},
},
- expectedErr: "",
- description: "label name with _",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "",
+ description: "label name with _",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "name"},
{Name: "@labelName\xff", Value: "labelValue"},
},
- expectedErr: "invalid label name: @labelName\xff",
- description: "label name with \xff",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "invalid label name: @labelName\xff",
+ description: "label name with \xff",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "name"},
{Name: "", Value: "labelValue"},
},
- expectedErr: "invalid label name: ",
- description: "label name is empty string",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "invalid label name: ",
+ description: "label name is empty string",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "name"},
{Name: "labelName", Value: string([]byte{0xff})},
},
- expectedErr: "invalid label value: " + string([]byte{0xff}),
- description: "label value is an invalid UTF-8 value",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "invalid label value: " + string([]byte{0xff}),
+ description: "label value is an invalid UTF-8 value",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "invalid_name\xff"},
},
- expectedErr: "invalid metric name: invalid_name\xff",
- description: "metric name has invalid utf8",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "invalid metric name: invalid_name\xff",
+ description: "metric name has invalid utf8",
},
{
input: []prompb.Label{
{Name: "__name__", Value: "name1"},
{Name: "__name__", Value: "name2"},
},
- expectedErr: "duplicate label with name: __name__",
- description: "duplicate label names",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "duplicate label with name: __name__",
+ description: "duplicate label names",
},
{
input: []prompb.Label{
{Name: "label1", Value: "name"},
{Name: "label2", Value: "name"},
},
- expectedErr: "",
- description: "duplicate label values",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "",
+ description: "duplicate label values",
},
{
input: []prompb.Label{
{Name: "", Value: "name"},
{Name: "label2", Value: "name"},
},
- expectedErr: "invalid label name: ",
- description: "don't report as duplicate label name",
+ validationScheme: model.UTF8Validation,
+ expectedErr: "invalid label name: ",
+ description: "don't report as duplicate label name",
+ },
+ {
+ input: []prompb.Label{
+ {Name: "__name__", Value: "name1*"},
+ },
+ validationScheme: model.LegacyValidation,
+ expectedErr: "invalid metric name: name1*",
+ description: "invalid legacy metric name",
+ },
+ {
+ input: []prompb.Label{
+ {Name: "label1*", Value: "name"},
+ },
+ validationScheme: model.LegacyValidation,
+ expectedErr: "invalid label name: label1*",
+ description: "invalid legacy label name",
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
- err := validateLabelsAndMetricName(test.input)
+ err := validateLabelsAndMetricName(test.input, test.validationScheme)
if test.expectedErr != "" {
require.EqualError(t, err, test.expectedErr)
} else {
diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go
index 11501aa15..a0faffc26 100644
--- a/storage/remote/queue_manager_test.go
+++ b/storage/remote/queue_manager_test.go
@@ -1751,7 +1751,7 @@ func TestQueueManagerMetrics(t *testing.T) {
metrics := newQueueManagerMetrics(reg, "name", "http://localhost:1234")
// Make sure metrics pass linting.
- problems, err := client_testutil.GatherAndLint(reg)
+ problems, err := client_testutil.GatherAndLint(reg, model.UTF8Validation)
require.NoError(t, err)
require.Empty(t, problems, "Metric linting problems detected: %v", problems)
diff --git a/storage/remote/storage.go b/storage/remote/storage.go
index ba6d100bd..721efa819 100644
--- a/storage/remote/storage.go
+++ b/storage/remote/storage.go
@@ -115,6 +115,10 @@ func (s *Storage) ApplyConfig(conf *config.Config) error {
if rrConf.Name != "" {
name = rrConf.Name
}
+ validationScheme := rrConf.MetricNameValidationScheme
+ if validationScheme == model.UnsetValidation {
+ validationScheme = model.UTF8Validation
+ }
c, err := NewReadClient(name, &ClientConfig{
URL: rrConf.URL,
@@ -122,6 +126,7 @@ func (s *Storage) ApplyConfig(conf *config.Config) error {
ChunkedReadLimit: rrConf.ChunkedReadLimit,
HTTPClientConfig: rrConf.HTTPClientConfig,
Headers: rrConf.Headers,
+ ValidationScheme: validationScheme,
})
if err != nil {
return err
diff --git a/web/api/v1/api.go b/web/api/v1/api.go
index bb17c8a33..c311fc771 100644
--- a/web/api/v1/api.go
+++ b/web/api/v1/api.go
@@ -14,6 +14,7 @@
package v1
import (
+ "cmp"
"context"
"crypto/sha1"
"encoding/hex"
@@ -200,6 +201,9 @@ type API struct {
QueryEngine promql.QueryEngine
ExemplarQueryable storage.ExemplarQueryable
+ // ValidationScheme for label and metric names. Defaults to utf-8.
+ ValidationScheme model.ValidationScheme
+
scrapePoolsRetriever func(context.Context) ScrapePoolsRetriever
targetRetriever func(context.Context) TargetRetriever
alertmanagerRetriever func(context.Context) AlertmanagerRetriever
@@ -270,6 +274,7 @@ func NewAPI(
QueryEngine: qe,
Queryable: q,
ExemplarQueryable: eq,
+ ValidationScheme: model.UTF8Validation,
scrapePoolsRetriever: spsr,
targetRetriever: tr,
@@ -462,7 +467,7 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
defer cancel()
}
- opts, err := extractQueryOpts(r)
+ opts, err := extractQueryOpts(r, cmp.Or(api.ValidationScheme, model.UTF8Validation))
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
}
@@ -528,7 +533,7 @@ func (api *API) parseQuery(r *http.Request) apiFuncResult {
return apiFuncResult{data: translateAST(expr), err: nil, warnings: nil, finalizer: nil}
}
-func extractQueryOpts(r *http.Request) (promql.QueryOpts, error) {
+func extractQueryOpts(r *http.Request, scheme model.ValidationScheme) (promql.QueryOpts, error) {
var duration time.Duration
if strDuration := r.FormValue("lookback_delta"); strDuration != "" {
@@ -539,7 +544,7 @@ func extractQueryOpts(r *http.Request) (promql.QueryOpts, error) {
duration = parsedDuration
}
- return promql.NewPrometheusQueryOpts(r.FormValue("stats") == "all", duration), nil
+ return promql.NewPrometheusQueryOpts(r.FormValue("stats") == "all", duration, scheme), nil
}
func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
@@ -587,7 +592,7 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
defer cancel()
}
- opts, err := extractQueryOpts(r)
+ opts, err := extractQueryOpts(r, cmp.Or(api.ValidationScheme, model.UTF8Validation))
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil}
}
@@ -782,8 +787,7 @@ func (api *API) labelValues(r *http.Request) (result apiFuncResult) {
name = model.UnescapeName(name, model.ValueEncodingEscaping)
}
- label := model.LabelName(name)
- if !label.IsValid() {
+ if !model.LabelName(name).IsValid(cmp.Or(api.ValidationScheme, model.UTF8Validation)) {
return apiFuncResult{nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}, nil, nil}
}
diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go
index 254ce074c..9db7d23a3 100644
--- a/web/api/v1/api_test.go
+++ b/web/api/v1/api_test.go
@@ -4611,7 +4611,7 @@ func TestExtractQueryOpts(t *testing.T) {
form: url.Values{
"stats": []string{"all"},
},
- expect: promql.NewPrometheusQueryOpts(true, 0),
+ expect: promql.NewPrometheusQueryOpts(true, 0, model.UTF8Validation),
err: nil,
},
@@ -4620,7 +4620,7 @@ func TestExtractQueryOpts(t *testing.T) {
form: url.Values{
"stats": []string{"none"},
},
- expect: promql.NewPrometheusQueryOpts(false, 0),
+ expect: promql.NewPrometheusQueryOpts(false, 0, model.UTF8Validation),
err: nil,
},
{
@@ -4629,7 +4629,7 @@ func TestExtractQueryOpts(t *testing.T) {
"stats": []string{"all"},
"lookback_delta": []string{"30s"},
},
- expect: promql.NewPrometheusQueryOpts(true, 30*time.Second),
+ expect: promql.NewPrometheusQueryOpts(true, 30*time.Second, model.UTF8Validation),
err: nil,
},
{
@@ -4645,7 +4645,7 @@ func TestExtractQueryOpts(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := &http.Request{Form: test.form}
- opts, err := extractQueryOpts(req)
+ opts, err := extractQueryOpts(req, model.UTF8Validation)
require.Equal(t, test.expect, opts)
if test.err == nil {
require.NoError(t, err)