Skip to content
Draft
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
14 changes: 7 additions & 7 deletions expfmt/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

// Decoder types decode an input stream into metric families.
type Decoder interface {
Decode(*dto.MetricFamily) error
Decode(*dto.MetricFamily, model.ValidationScheme) error
}

// DecodeOptions contains options used by the Decoder and in sample extraction.
Expand Down Expand Up @@ -86,14 +86,14 @@ type protoDecoder struct {
}

// Decode implements the Decoder interface.
func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
func (d *protoDecoder) Decode(v *dto.MetricFamily, nameValidationScheme model.ValidationScheme) error {
opts := protodelim.UnmarshalOptions{
MaxSize: -1,
}
if err := opts.UnmarshalFrom(d.r, v); err != nil {
return err
}
if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
if !model.IsValidMetricName(model.LabelValue(v.GetName()), nameValidationScheme) {
return fmt.Errorf("invalid metric name %q", v.GetName())
}
for _, m := range v.GetMetric() {
Expand All @@ -107,7 +107,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
if !model.LabelValue(l.GetValue()).IsValid() {
return fmt.Errorf("invalid label value %q", l.GetValue())
}
if !model.LabelName(l.GetName()).IsValid() {
if !model.LabelName(l.GetName()).IsValid(nameValidationScheme) {
return fmt.Errorf("invalid label name %q", l.GetName())
}
}
Expand All @@ -123,7 +123,7 @@ type textDecoder struct {
}

// Decode implements the Decoder interface.
func (d *textDecoder) Decode(v *dto.MetricFamily) error {
func (d *textDecoder) Decode(v *dto.MetricFamily, _ model.ValidationScheme) error {
if d.err == nil {
// Read all metrics in one shot.
var p TextParser
Expand Down Expand Up @@ -156,8 +156,8 @@ type SampleDecoder struct {

// Decode calls the Decode method of the wrapped Decoder and then extracts the
// samples from the decoded MetricFamily into the provided model.Vector.
func (sd *SampleDecoder) Decode(s *model.Vector) error {
err := sd.Dec.Decode(&sd.f)
func (sd *SampleDecoder) Decode(s *model.Vector, nameValidationScheme model.ValidationScheme) error {
err := sd.Dec.Decode(&sd.f, nameValidationScheme)
if err != nil {
return err
}
Expand Down
12 changes: 5 additions & 7 deletions expfmt/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ mf2 4
var all model.Vector
for {
var smpls model.Vector
err := dec.Decode(&smpls)
err := dec.Decode(&smpls, model.UTF8Validation)
if err != nil && errors.Is(err, io.EOF) {
break
}
Expand Down Expand Up @@ -369,22 +369,20 @@ func TestProtoDecoder(t *testing.T) {

var all model.Vector
for {
model.NameValidationScheme = model.LegacyValidation //nolint:staticcheck
var smpls model.Vector
err := dec.Decode(&smpls)
err := dec.Decode(&smpls, model.LegacyValidation)
if err != nil && errors.Is(err, io.EOF) {
break
}
if scenario.legacyNameFail {
require.Errorf(t, err, "Expected error when decoding without UTF-8 support enabled but got none")
model.NameValidationScheme = model.UTF8Validation //nolint:staticcheck
dec = &SampleDecoder{
Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
Opts: &DecodeOptions{
Timestamp: testTime,
},
}
err = dec.Decode(&smpls)
err = dec.Decode(&smpls, model.UTF8Validation)
if errors.Is(err, io.EOF) {
break
}
Expand Down Expand Up @@ -412,7 +410,7 @@ func TestProtoMultiMessageDecoder(t *testing.T) {
var metrics []*dto.MetricFamily
for {
var mf dto.MetricFamily
if err := decoder.Decode(&mf); err != nil {
if err := decoder.Decode(&mf, model.UTF8Validation); err != nil {
if errors.Is(err, io.EOF) {
break
}
Expand Down Expand Up @@ -560,7 +558,7 @@ func TestTextDecoderWithBufioReader(t *testing.T) {
dec := NewDecoder(r, FmtText)
for {
var mf dto.MetricFamily
if err := dec.Decode(&mf); err != nil {
if err := dec.Decode(&mf, model.UTF8Validation); err != nil {
if errors.Is(err, io.EOF) {
break
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ require (
)

retract v0.50.0 // Critical bug in counter suffixes, please read issue https://github.com/prometheus/common/issues/605

replace github.com/prometheus/client_golang => github.com/juliusmh/client_golang v1.22.1-0.20250701110037-ceb5803cbf1f
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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/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=
Expand All @@ -29,8 +29,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
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/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
Expand Down
6 changes: 3 additions & 3 deletions model/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ func (a *Alert) StatusAt(ts time.Time) AlertStatus {
}

// Validate checks whether the alert data is inconsistent.
func (a *Alert) Validate() error {
func (a *Alert) Validate(nameValidationScheme ValidationScheme) error {
if a.StartsAt.IsZero() {
return errors.New("start time missing")
}
if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) {
return errors.New("start time must be before end time")
}
if err := a.Labels.Validate(); err != nil {
if err := a.Labels.Validate(nameValidationScheme); err != nil {
return fmt.Errorf("invalid label set: %w", err)
}
if len(a.Labels) == 0 {
return errors.New("at least one label pair required")
}
if err := a.Annotations.Validate(); err != nil {
if err := a.Annotations.Validate(nameValidationScheme); err != nil {
return fmt.Errorf("invalid annotations: %w", err)
}
return nil
Expand Down
36 changes: 21 additions & 15 deletions model/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,91 +22,97 @@ import (
)

func TestAlertValidate(t *testing.T) {
oldScheme := NameValidationScheme
NameValidationScheme = LegacyValidation
defer func() {
NameValidationScheme = oldScheme
}()
ts := time.Now()

cases := []struct {
alert *Alert
err string
alert *Alert
err string
scheme ValidationScheme
}{
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
},
scheme: LegacyValidation,
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
},
err: "start time missing",
scheme: LegacyValidation,
err: "start time missing",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts,
},
scheme: LegacyValidation,
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts.Add(1 * time.Minute),
},
scheme: LegacyValidation,
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts.Add(-1 * time.Minute),
},
err: "start time must be before end time",
scheme: LegacyValidation,
err: "start time must be before end time",
},
{
alert: &Alert{
StartsAt: ts,
},
err: "at least one label pair required",
scheme: LegacyValidation,
err: "at least one label pair required",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b", "!bad": "label"},
StartsAt: ts,
},
err: "invalid label set: invalid name",
scheme: LegacyValidation,
err: "invalid label set: invalid name",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b", "bad": "\xfflabel"},
StartsAt: ts,
},
err: "invalid label set: invalid value",
scheme: LegacyValidation,
err: "invalid label set: invalid value",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
Annotations: LabelSet{"!bad": "label"},
StartsAt: ts,
},
err: "invalid annotations: invalid name",
scheme: LegacyValidation,
err: "invalid annotations: invalid name",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
Annotations: LabelSet{"bad": "\xfflabel"},
StartsAt: ts,
},
err: "invalid annotations: invalid value",
scheme: LegacyValidation,
err: "invalid annotations: invalid value",
},
}

for i, c := range cases {
err := c.alert.Validate()
err := c.alert.Validate(c.scheme)
if err == nil {
if c.err == "" {
continue
Expand Down
15 changes: 8 additions & 7 deletions model/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,18 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
type LabelName string

// IsValid returns true iff the name matches the pattern of LabelNameRE when
// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if
// NameValidationScheme is set to UTF8Validation.
func (ln LabelName) IsValid() bool {
// scheme is LegacyValidation, or valid UTF-8 if it is UTF8Validation.
func (ln LabelName) IsValid(scheme ValidationScheme) bool {
if len(ln) == 0 {
return false
}
switch NameValidationScheme {
switch scheme {
case LegacyValidation:
return ln.IsValidLegacy()
case UTF8Validation:
return utf8.ValidString(string(ln))
default:
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
panic(fmt.Sprintf("Invalid name validation scheme requested: %s", scheme.String()))
}
}

Expand All @@ -137,25 +136,27 @@ func (ln LabelName) IsValidLegacy() bool {
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
// Validation is done using UTF8Validation.
func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
if !LabelName(s).IsValid() {
if !LabelName(s).IsValid(UTF8Validation) {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
return nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// Validation is done using UTF8Validation.
func (ln *LabelName) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if !LabelName(s).IsValid() {
if !LabelName(s).IsValid(UTF8Validation) {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
Expand Down
6 changes: 2 additions & 4 deletions model/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,13 @@ func TestLabelNameIsValid(t *testing.T) {
}

for _, s := range scenarios {
NameValidationScheme = LegacyValidation
if s.ln.IsValid() != s.legacyValid {
if s.ln.IsValid(LegacyValidation) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
}
if LabelNameRE.MatchString(string(s.ln)) != s.legacyValid {
t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
}
NameValidationScheme = UTF8Validation
if s.ln.IsValid() != s.utf8Valid {
if s.ln.IsValid(UTF8Validation) != s.utf8Valid {
t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.legacyValid, s.ln)
}
}
Expand Down
7 changes: 4 additions & 3 deletions model/labelset.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ type LabelSet map[LabelName]LabelValue

// Validate checks whether all names and values in the label set
// are valid.
func (ls LabelSet) Validate() error {
func (ls LabelSet) Validate(nameValidationScheme ValidationScheme) error {
for ln, lv := range ls {
if !ln.IsValid() {
if !ln.IsValid(nameValidationScheme) {
return fmt.Errorf("invalid name %q", ln)
}
if !lv.IsValid() {
Expand Down Expand Up @@ -140,6 +140,7 @@ func (ls LabelSet) FastFingerprint() Fingerprint {
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// Validates label names using UTF8Validation.
func (l *LabelSet) UnmarshalJSON(b []byte) error {
var m map[LabelName]LabelValue
if err := json.Unmarshal(b, &m); err != nil {
Expand All @@ -149,7 +150,7 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error {
// LabelName as a string and does not call its UnmarshalJSON method.
// Thus, we have to replicate the behavior here.
for ln := range m {
if !ln.IsValid() {
if !ln.IsValid(UTF8Validation) {
return fmt.Errorf("%q is not a valid label name", ln)
}
}
Expand Down
10 changes: 5 additions & 5 deletions model/labelset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package model
import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
)

func TestUnmarshalJSONLabelSet(t *testing.T) {
Expand Down Expand Up @@ -55,12 +57,10 @@ func TestUnmarshalJSONLabelSet(t *testing.T) {
}
}`

NameValidationScheme = LegacyValidation
err = json.Unmarshal([]byte(invalidlabelSetJSON), &c)
expectedErr := `"1nvalid_23name" is not a valid label name`
if err == nil || err.Error() != expectedErr {
t.Errorf("expected an error with message '%s' to be thrown", expectedErr)
}
require.NoError(t, err)
err = c.LabelSet.Validate(LegacyValidation)
require.EqualError(t, err, `invalid name "1nvalid_23name"`)
}

func TestLabelSetClone(t *testing.T) {
Expand Down
Loading
Loading