Skip to content

Conversation

marcodebba
Copy link
Contributor

Fixes #11486

Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Comment on lines +585 to +598
func ParseFile(fn string) (interface{}, error) {
content, err := os.ReadFile(fn)
if err != nil {
return nil, err
}

var rules interface{}
err = yaml.Unmarshal(content, &rules)
if err != nil {
return nil, err
}

return rules, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an existing function in the rulefmt package in prometheus called ParseFile() that seems to do both yaml validation as well as validation of the literal rule groups. Is this new function necessary?

// ParseFile reads and parses rules from a file.
func ParseFile(file string, ignoreUnknownFields bool) (*RuleGroups, []error) {
b, err := os.ReadFile(file)
if err != nil {
return nil, []error{fmt.Errorf("%s: %w", file, err)}
}
rgs, errs := Parse(b, ignoreUnknownFields)
for i := range errs {
errs[i] = fmt.Errorf("%s: %w", file, errs[i])
}
return rgs, errs
}

.idea/modules.xml
.idea/prometheus.iml
.idea/vcs.xml
.idea/workspace.xml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should go in your global git ignore file:
https://sebastiandedeyne.com/setting-up-a-global-gitignore-file/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spurious lines are still in here. Please remove them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, let me fix this evening . Thanks for suggestions

@aknuds1 aknuds1 requested a review from Copilot April 25, 2025 07:29
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes #11486 by introducing early parsing and validation of alerting rules before loading the WAL.

  • Added a new ParseFile function in rules/manager.go to parse YAML rule files.
  • Introduced a TestParseFile unit test in rules/manager_test.go to verify YAML parsing.
  • Updated cmd/prometheus/main.go to integrate rule file parsing and exit early on errors.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
rules/manager_test.go Added TestParseFile to verify YAML parsing of rule files.
rules/manager.go Introduced ParseFile to load and unmarshal YAML rule definitions.
cmd/prometheus/main.go Integrated rule file parsing and error handling during startup.
Comments suppressed due to low confidence (1)

rules/manager_test.go:2587

  • [nitpick] To improve test coverage and robustness, add concrete assertions to validate the structure of the parsed rules rather than checking only for a non-nil result.
// Optionally, you can add more assertions to validate the structure of the parsed rules.

Copy link
Contributor

@aknuds1 aknuds1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @tjhop that rulefmt.ParseFile should be used.

Comment on lines +630 to +665

// This section of the code is responsible for validating and parsing rule files
// specified in the Prometheus configuration. It ensures that all rule files
// match the provided patterns and are syntactically correct before Prometheus
// starts processing them.
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`).
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files.
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code.
// 4. For each matching file, attempt to parse it using `rules.ParseFile`.
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code.
//
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered.

if len(cfgFile.RuleFiles) > 0 {
for _, pat := range cfgFile.RuleFiles {
files, err := filepath.Glob(pat)
if err != nil {
absPath, pathErr := filepath.Abs(pat)
if pathErr != nil {
absPath = pat
}
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err)
os.Exit(2)
}
for _, fn := range files {
if _, err := rules.ParseFile(fn); err != nil {
absPath, pathErr := filepath.Abs(fn)
if pathErr != nil {
absPath = fn
}
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err)
os.Exit(2)
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to fully embed the logic in a function in the rules package:

Suggested change
// This section of the code is responsible for validating and parsing rule files
// specified in the Prometheus configuration. It ensures that all rule files
// match the provided patterns and are syntactically correct before Prometheus
// starts processing them.
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`).
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files.
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code.
// 4. For each matching file, attempt to parse it using `rules.ParseFile`.
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code.
//
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered.
if len(cfgFile.RuleFiles) > 0 {
for _, pat := range cfgFile.RuleFiles {
files, err := filepath.Glob(pat)
if err != nil {
absPath, pathErr := filepath.Abs(pat)
if pathErr != nil {
absPath = pat
}
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err)
os.Exit(2)
}
for _, fn := range files {
if _, err := rules.ParseFile(fn); err != nil {
absPath, pathErr := filepath.Abs(fn)
if pathErr != nil {
absPath = fn
}
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err)
os.Exit(2)
}
}
}
}
if err := rules.ParseFiles(cfgFile.RuleFiles); err != nil {
absPath, pathErr := filepath.Abs(cfg.configFile)
if pathErr != nil {
absPath = cfg.configFile
}
logger.Error(fmt.Sprintf("Error loading rule file patterns from config (--config.file=%q)", cfg.configFile), "file", absPath, "err", err)
os.Exit(2)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment hasn't been addressed yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used the existing function and deleted my not useful method.

Comment on lines +585 to +598
func ParseFile(fn string) (interface{}, error) {
content, err := os.ReadFile(fn)
if err != nil {
return nil, err
}

var rules interface{}
err = yaml.Unmarshal(content, &rules)
if err != nil {
return nil, err
}

return rules, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following makes more sense to me:

Suggested change
func ParseFile(fn string) (interface{}, error) {
content, err := os.ReadFile(fn)
if err != nil {
return nil, err
}
var rules interface{}
err = yaml.Unmarshal(content, &rules)
if err != nil {
return nil, err
}
return rules, nil
}
// ParseFiles parses the rule files corresponding to glob patterns.
func ParseFiles(patterns []string) error {
files := map[string]string{}
for _, pat := range patterns {
fns, err := filepath.Glob(pat)
if err != nil {
return fmt.Errorf("failed retrieving rule files for %q: %w", pat, err)
}
for _, fn := range fns {
absPath, err := filepath.Abs(fn)
if err != nil {
absPath = fn
}
cleanPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
return fmt.Errorf("failed evaluating rule file path %q (pattern: %q): %w", absPath, pat, err)
}
files[cleanPath] = pat
}
}
for fn, pat := range files {
_, errs := rulefmt.ParseFile(fn, false)
if len(errs) > 0 {
return fmt.Errorf("parse rules from file %q (pattern: %q): %w", fn, pat, errors.Join(errs...))
}
}
return nil
}

Comment on lines +2555 to +2590
func TestParseFile(t *testing.T) {
// Create a temporary directory for the test.
tmpDir := t.TempDir()

// Define the test file path.
testFile := filepath.Join(tmpDir, "test_rules.yml")

// Define the content of the test YAML file.
testContent := `
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "High request latency"
`

// Write the test content to the file.
err := os.WriteFile(testFile, []byte(testContent), 0o644)
require.NoError(t, err)

// Call the ParseFile function.
result, err := ParseFile(testFile)
require.NoError(t, err)

// Assert that the result is not nil.
require.NotNil(t, result, "Expected parsed rules to be non-nil")

// Optionally, you can add more assertions to validate the structure of the parsed rules.
// For example, if you know the expected structure, you can cast `result` to the appropriate type
// and check its fields.
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified as follows, even with an error case added. Note that the error case needs an extra file invalid-rules.yaml in rules/fixtures/.

Suggested change
func TestParseFile(t *testing.T) {
// Create a temporary directory for the test.
tmpDir := t.TempDir()
// Define the test file path.
testFile := filepath.Join(tmpDir, "test_rules.yml")
// Define the content of the test YAML file.
testContent := `
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "High request latency"
`
// Write the test content to the file.
err := os.WriteFile(testFile, []byte(testContent), 0o644)
require.NoError(t, err)
// Call the ParseFile function.
result, err := ParseFile(testFile)
require.NoError(t, err)
// Assert that the result is not nil.
require.NotNil(t, result, "Expected parsed rules to be non-nil")
// Optionally, you can add more assertions to validate the structure of the parsed rules.
// For example, if you know the expected structure, you can cast `result` to the appropriate type
// and check its fields.
}
func TestParseFiles(t *testing.T) {
t.Run("good files", func(t *testing.T) {
err := ParseFiles([]string{filepath.Join("fixtures", "rules.y*ml")})
require.NoError(t, err)
})
t.Run("bad files", func(t *testing.T) {
err := ParseFiles([]string{filepath.Join("fixtures", "invalid-rules.y*ml")})
require.ErrorContains(t, err, "field unexpected_field not found in type rulefmt.Rule")
})
}

Copy link
Member

@beorn7 beorn7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tjhop and @aknuds1 for your reviews.

@marcodebba you resolved all the comments, but you haven't really addressed them.
Have you maybe forgotten to push your latest changes?

I agree with everything the reviewers said, and I think you should address their comments.

.idea/modules.xml
.idea/prometheus.iml
.idea/vcs.xml
.idea/workspace.xml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spurious lines are still in here. Please remove them.

Comment on lines +630 to +665

// This section of the code is responsible for validating and parsing rule files
// specified in the Prometheus configuration. It ensures that all rule files
// match the provided patterns and are syntactically correct before Prometheus
// starts processing them.
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`).
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files.
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code.
// 4. For each matching file, attempt to parse it using `rules.ParseFile`.
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code.
//
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered.

if len(cfgFile.RuleFiles) > 0 {
for _, pat := range cfgFile.RuleFiles {
files, err := filepath.Glob(pat)
if err != nil {
absPath, pathErr := filepath.Abs(pat)
if pathErr != nil {
absPath = pat
}
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err)
os.Exit(2)
}
for _, fn := range files {
if _, err := rules.ParseFile(fn); err != nil {
absPath, pathErr := filepath.Abs(fn)
if pathErr != nil {
absPath = fn
}
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err)
os.Exit(2)
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment hasn't been addressed yet.

Comment on lines +645 to +655
// This section of the code is responsible for validating and parsing rule files
// specified in the Prometheus configuration. It ensures that all rule files
// match the provided patterns and are syntactically correct before Prometheus
// starts processing them.
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`).
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files.
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code.
// 4. For each matching file, attempt to parse it using `rules.ParseFile`.
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code.
//
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I like readable comments in proper English, this one is a bit too verbose.

A general good advice is that code should not be an English version of the Go code. You can assume that the reader of your code knows Go well (maybe even better than English ;).

If you move the code below into a function rules.ParseFiles, your comment here can be its doc comment, but it should still focus more on what the function does rather than how.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for suggestions @beorn7 . I'll try to fix everything today

subhramit added a commit to subhramit/prometheus that referenced this pull request May 15, 2025
Co-authored-by:
marcodebba <marcodebonis74@gmail.com>
Co-authored-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
subhramit added a commit to subhramit/prometheus that referenced this pull request May 15, 2025
Co-authored-by: marcodebba <marcodebonis74@gmail.com>
Co-authored-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
subhramit added a commit to subhramit/prometheus that referenced this pull request May 15, 2025
Builds over prometheus#16462

Co-authored-by: marcodebba <marcodebonis74@gmail.com>

Co-authored-by: subhramit <subhramit.bb@live.in>
Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
subhramit added a commit to subhramit/prometheus that referenced this pull request May 15, 2025
Builds over prometheus#16462
Addresses reviews, adds invalid rule test file

Co-authored-by: marcodebba <marcodebon1s74@gmail.com>
Co-authored-by: subhramit <subhramit.bb@live.in>
Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
subhramit added a commit to subhramit/prometheus that referenced this pull request May 15, 2025
Builds over prometheus#16462
Addresses comments, adds invalid rules file

Co-authored-by: marcodebba <marcodebonis74@gmail.com>
Co-authored-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@beorn7
Copy link
Member

beorn7 commented May 16, 2025

Closing in lieu of #16601 .

@beorn7 beorn7 closed this May 16, 2025
aknuds1 pushed a commit that referenced this pull request May 26, 2025
Builds over #16462
Addresses comments, adds invalid rules file

Signed-off-by: subhramit <subhramit.bb@live.in>
Co-authored-by: marcodebba <marcodebonis74@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

parse alerting before loading WAL
4 participants