diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 588605d44e..93e1bb20ef 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -873,7 +873,7 @@ func checkRulesFromStdin(ls rulesLintConfig) (bool, bool) { fmt.Fprintln(os.Stderr, " FAILED:", err) return true, true } - rgs, errs := rulefmt.Parse(data, ls.ignoreUnknownFields, ls.nameValidationScheme) + rgs, errs := rulefmt.Parse(data, ls.ignoreUnknownFields, ls.nameValidationScheme, promtoolParserOpts) if errs != nil { failed = true fmt.Fprintln(os.Stderr, " FAILED:") @@ -907,7 +907,7 @@ func checkRules(files []string, ls rulesLintConfig) (bool, bool) { hasErrors := false for _, f := range files { fmt.Println("Checking", f) - rgs, errs := rulefmt.ParseFile(f, ls.ignoreUnknownFields, ls.nameValidationScheme) + rgs, errs := rulefmt.ParseFile(f, ls.ignoreUnknownFields, ls.nameValidationScheme, promtoolParserOpts) if errs != nil { failed = true fmt.Fprintln(os.Stderr, " FAILED:") diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index 68d145795a..c64596dbe5 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -37,6 +37,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/rulefmt" + "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/promqltest" ) @@ -187,7 +188,7 @@ func TestCheckDuplicates(t *testing.T) { c := test t.Run(c.name, func(t *testing.T) { t.Parallel() - rgs, err := rulefmt.ParseFile(c.ruleFile, false, model.UTF8Validation) + rgs, err := rulefmt.ParseFile(c.ruleFile, false, model.UTF8Validation, parser.Options{}) require.Empty(t, err) dups := checkDuplicates(rgs.Groups) require.Equal(t, c.expectedDups, dups) @@ -196,7 +197,7 @@ func TestCheckDuplicates(t *testing.T) { } func BenchmarkCheckDuplicates(b *testing.B) { - rgs, err := rulefmt.ParseFile("./testdata/rules_large.yml", false, model.UTF8Validation) + rgs, err := rulefmt.ParseFile("./testdata/rules_large.yml", false, model.UTF8Validation, parser.Options{}) require.Empty(b, err) for b.Loop() { diff --git a/model/rulefmt/rulefmt.go b/model/rulefmt/rulefmt.go index 2cbfdf4cfc..bf72a863aa 100644 --- a/model/rulefmt/rulefmt.go +++ b/model/rulefmt/rulefmt.go @@ -97,7 +97,7 @@ type ruleGroups struct { } // Validate validates all rules in the rule groups. -func (g *RuleGroups) Validate(node ruleGroups, nameValidationScheme model.ValidationScheme) (errs []error) { +func (g *RuleGroups) Validate(node ruleGroups, nameValidationScheme model.ValidationScheme, parserOpts parser.Options) (errs []error) { if err := namevalidationutil.CheckNameValidationScheme(nameValidationScheme); err != nil { errs = append(errs, err) return errs @@ -134,7 +134,7 @@ func (g *RuleGroups) Validate(node ruleGroups, nameValidationScheme model.Valida set[g.Name] = struct{}{} for i, r := range g.Rules { - for _, node := range r.Validate(node.Groups[j].Rules[i], nameValidationScheme) { + for _, node := range r.Validate(node.Groups[j].Rules[i], nameValidationScheme, parserOpts) { 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, nameValidationScheme model.ValidationScheme) (nodes []WrappedError) { +func (r *Rule) Validate(node RuleNode, nameValidationScheme model.ValidationScheme, parserOpts parser.Options) (nodes []WrappedError) { if r.Record != "" && r.Alert != "" { nodes = append(nodes, WrappedError{ err: errors.New("only one of 'record' and 'alert' must be set"), @@ -219,7 +219,7 @@ func (r *Rule) Validate(node RuleNode, nameValidationScheme model.ValidationSche err: errors.New("field 'expr' must be set in rule"), node: &node.Expr, }) - } else if _, err := parser.ParseExpr(r.Expr); err != nil { + } else if _, err := parser.ParseExpr(r.Expr, parser.WithOptions(parserOpts)); err != nil { nodes = append(nodes, WrappedError{ err: fmt.Errorf("could not parse expression: %w", err), node: &node.Expr, @@ -339,7 +339,7 @@ func testTemplateParsing(rl *Rule) (errs []error) { } // Parse parses and validates a set of rules. -func Parse(content []byte, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*RuleGroups, []error) { +func Parse(content []byte, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme, parserOpts parser.Options) (*RuleGroups, []error) { var ( groups RuleGroups node ruleGroups @@ -364,16 +364,16 @@ func Parse(content []byte, ignoreUnknownFields bool, nameValidationScheme model. return nil, errs } - return &groups, groups.Validate(node, nameValidationScheme) + return &groups, groups.Validate(node, nameValidationScheme, parserOpts) } // ParseFile reads and parses rules from a file. -func ParseFile(file string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*RuleGroups, []error) { +func ParseFile(file string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme, parserOpts parser.Options) (*RuleGroups, []error) { b, err := os.ReadFile(file) if err != nil { return nil, []error{fmt.Errorf("%s: %w", file, err)} } - rgs, errs := Parse(b, ignoreUnknownFields, nameValidationScheme) + rgs, errs := Parse(b, ignoreUnknownFields, nameValidationScheme, parserOpts) 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 ea8d09af0d..52c2abfb1d 100644 --- a/model/rulefmt/rulefmt_test.go +++ b/model/rulefmt/rulefmt_test.go @@ -22,17 +22,20 @@ import ( "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "go.yaml.in/yaml/v3" + + "github.com/prometheus/prometheus/promql/parser" ) func TestParseFileSuccess(t *testing.T) { - _, errs := ParseFile("testdata/test.yaml", false, model.UTF8Validation) + opts := parser.Options{} + _, errs := ParseFile("testdata/test.yaml", false, model.UTF8Validation, opts) require.Empty(t, errs, "unexpected errors parsing file") - _, errs = ParseFile("testdata/utf-8_lname.good.yaml", false, model.UTF8Validation) + _, errs = ParseFile("testdata/utf-8_lname.good.yaml", false, model.UTF8Validation, opts) require.Empty(t, errs, "unexpected errors parsing file") - _, errs = ParseFile("testdata/utf-8_annotation.good.yaml", false, model.UTF8Validation) + _, errs = ParseFile("testdata/utf-8_annotation.good.yaml", false, model.UTF8Validation, opts) require.Empty(t, errs, "unexpected errors parsing file") - _, errs = ParseFile("testdata/legacy_validation_annotation.good.yaml", false, model.LegacyValidation) + _, errs = ParseFile("testdata/legacy_validation_annotation.good.yaml", false, model.LegacyValidation, opts) require.Empty(t, errs, "unexpected errors parsing file") } @@ -41,7 +44,7 @@ func TestParseFileSuccessWithAliases(t *testing.T) { / sum without(instance) (rate(requests_total[5m])) ` - rgs, errs := ParseFile("testdata/test_aliases.yaml", false, model.UTF8Validation) + rgs, errs := ParseFile("testdata/test_aliases.yaml", false, model.UTF8Validation, parser.Options{}) require.Empty(t, errs, "unexpected errors parsing file") for _, rg := range rgs.Groups { require.Equal(t, "HighAlert", rg.Rules[0].Alert) @@ -119,7 +122,7 @@ func TestParseFileFailure(t *testing.T) { if c.nameValidationScheme == model.UnsetValidation { c.nameValidationScheme = model.UTF8Validation } - _, errs := ParseFile(filepath.Join("testdata", c.filename), false, c.nameValidationScheme) + _, errs := ParseFile(filepath.Join("testdata", c.filename), false, c.nameValidationScheme, parser.Options{}) 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) }) @@ -215,7 +218,7 @@ groups: } for _, tst := range tests { - rgs, errs := Parse([]byte(tst.ruleString), false, model.UTF8Validation) + rgs, errs := Parse([]byte(tst.ruleString), false, model.UTF8Validation, parser.Options{}) require.NotNil(t, rgs, "Rule parsing, rule=\n"+tst.ruleString) passed := (tst.shouldPass && len(errs) == 0) || (!tst.shouldPass && len(errs) > 0) require.True(t, passed, "Rule validation failed, rule=\n"+tst.ruleString) @@ -242,7 +245,7 @@ groups: annotations: summary: "Instance {{ $labels.instance }} up" ` - _, errs := Parse([]byte(group), false, model.UTF8Validation) + _, errs := Parse([]byte(group), false, model.UTF8Validation, parser.Options{}) require.Len(t, errs, 2, "Expected two errors") var err00 *Error require.ErrorAs(t, errs[0], &err00) diff --git a/rules/manager.go b/rules/manager.go index 745680615c..7eec285a59 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -327,8 +327,8 @@ type FileLoader struct { ParserOptions parser.Options } -func (FileLoader) Load(identifier string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*rulefmt.RuleGroups, []error) { - return rulefmt.ParseFile(identifier, ignoreUnknownFields, nameValidationScheme) +func (fl FileLoader) Load(identifier string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*rulefmt.RuleGroups, []error) { + return rulefmt.ParseFile(identifier, ignoreUnknownFields, nameValidationScheme, fl.ParserOptions) } func (fl FileLoader) Parse(query string) (parser.Expr, error) { @@ -632,7 +632,7 @@ func ParseFiles(patterns []string, nameValidationScheme model.ValidationScheme) } } for fn, pat := range files { - _, errs := rulefmt.ParseFile(fn, false, nameValidationScheme) + _, errs := rulefmt.ParseFile(fn, false, nameValidationScheme, parser.Options{}) if len(errs) > 0 { return fmt.Errorf("parse rules from file %q (pattern: %q): %w", fn, pat, errors.Join(errs...)) } diff --git a/rules/manager_test.go b/rules/manager_test.go index 3fcb90808e..01874002a2 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -809,7 +809,7 @@ func TestUpdate(t *testing.T) { } // Groups will be recreated if updated. - rgs, errs := rulefmt.ParseFile("fixtures/rules.yaml", false, model.UTF8Validation) + rgs, errs := rulefmt.ParseFile("fixtures/rules.yaml", false, model.UTF8Validation, parser.Options{}) require.Empty(t, errs, "file parsing failures") tmpFile, err := os.CreateTemp("", "rules.test.*.yaml")