Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions cmd/prometheus/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions cmd/prometheus/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
23 changes: 14 additions & 9 deletions cmd/promtool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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...))
Expand Down Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions cmd/promtool/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
120 changes: 105 additions & 15 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package config

import (
"cmp"
"errors"
"fmt"
"log/slog"
Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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() &&
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading