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)